Android 占位式插件化开发
程序员文章站
2022-06-24 19:49:13
先上效果图项目结构:app:宿主apkplugin:插件apkstandard:标准,插件activity创建时候需要实现里面的接口1、创建标准/** * activity标准接口 */public interface ActivityInterface { /** * 把宿主的环境给 插件 * @param activity */ void insertAppActivity(Activity activity); void...
先上效果图
项目结构:
app:宿主apk
plugin:插件apk
standard:标准,插件activity创建时候需要实现里面的接口
1、创建标准
/**
* activity标准接口
*/
public interface ActivityInterface {
/**
* 把宿主的环境给 插件
* @param activity
*/
void insertAppActivity(Activity activity);
void onCreate(Bundle savedInstanceState);
void onResume() ;
void onStart();
void onDestroy();
}
2、创建插件apk
tips:其中activity在插件中无需注册
创建BaseActivity,实现ActivityInterface
public class BaseActivity extends Activity implements ActivityInterface {
//宿主环境,因为插件未安装是没有上下文的
public Activity activity;
@Override
public void insertAppActivity(Activity activity) {
this.activity = activity;
}
@SuppressLint("MissingSuperCall")
@Override
public void onCreate(Bundle savedInstanceState) {
}
@SuppressLint("MissingSuperCall")
@Override
public void onResume() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onStart() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onDestroy() {
}
public void setContentView(int layoutResID) {
activity.setContentView(layoutResID);
}
public <T extends View> T findViewById(int id) {
return activity.findViewById(id);
}
@Override
public void startActivity(Intent intent) {
Intent intentNew = new Intent();
intentNew.putExtra("className",intent.getComponent().getClassName());
activity.startActivity(intentNew);
}
}
创建PluginActivity
public class PluginActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plugin);
// this 会报错,因为插件没有安装,也没有组件的环境,所以必须使用宿主环境
Toast.makeText(activity, "我是插件", Toast.LENGTH_SHORT).show();
findViewById(R.id.bt_start_activity).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(activity, TestActivity.class));
}
});
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".PluginActivity"
android:background="@android:color/holo_blue_bright"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- android:onClick="start" 会报错,因为在插件里面 没有组件环境 -->
<Button
android:id="@+id/bt_start_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="插件内部跳转插件的Activity"
/>
</LinearLayout>
插件内部跳转TestActivity
public class TestActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
3、创建宿主apk内容
tips:首先是要配置权限,因为我将插件apk放在了sdcard目录下,所以需要配置权限,同时需要动态申请权限,否则每次都获取不到插件中的activity信息
Manifest中配置
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
public class MainActivity extends AppCompatActivity {
// 要申请的权限
private String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
};
//未授权的权限
private List<String> mPermissionList = new ArrayList<>();
//权限请求码
private final int mRequestCode = 100;
private AlertDialog mPermissionDialog;
private String mPackName = "com.example.pluginproject";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initPermission();
}
private void initPermission() {
mPermissionList.clear();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//逐个判断是否还有未通过的权限
for (int i = 0; i < permissions.length; i++) {
if (ContextCompat.checkSelfPermission(this, permissions[i]) !=
PackageManager.PERMISSION_GRANTED) {
mPermissionList.add(permissions[i]);//添加还未授予的权限到mPermissionList中
}
}
//申请权限
if (mPermissionList.size() > 0) {//有权限没有通过,需要申请
ActivityCompat.requestPermissions(this, permissions, mRequestCode);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
boolean hasPermissionDismiss = false;//有权限没有通过
if (mRequestCode == requestCode) {
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == -1) {
hasPermissionDismiss = true;
break;
}
}
}
if (hasPermissionDismiss) {//如果有没有被允许的权限
showPermissionDialog();
}
}
/**
* 不再提示权限时的展示对话框
*/
private void showPermissionDialog() {
if (mPermissionDialog == null) {
mPermissionDialog = new AlertDialog.Builder(this)
.setMessage("已禁用权限,请手动授予")
.setPositiveButton("设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
cancelPermissionDialog();
Uri packageURI = Uri.parse("package:" + mPackName);
Intent intent = new Intent(Settings.
ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
startActivity(intent);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//关闭页面或者做其他操作
cancelPermissionDialog();
MainActivity.this.finish();
}
})
.create();
}
mPermissionDialog.show();
}
private void cancelPermissionDialog() {
mPermissionDialog.cancel();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
public void loadPlugin(View view) {
PluginManager.getInstance(this).loadPlugin();
}
// 启动插件里面的Activity
public void startPluginActivity(View view) {
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
String path = file.getAbsolutePath();
// 获取插件包 里面的 Activity
PackageManager packageManager = getPackageManager();
PackageInfo packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
ActivityInfo activityInfo = packageInfo.activities[0];
// 占位 代理Activity
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className", activityInfo.name);
startActivity(intent);
}
}
插件加载管理PluginManager
/**
* 插件管理,负责加载插件类
*/
public class PluginManager {
private static final String TAG = PluginManager.class.getSimpleName();
private static PluginManager pluginManager;
private Context context;
public static PluginManager getInstance(Context context){
if (pluginManager == null){
synchronized (PluginManager.class){
if (pluginManager == null){
pluginManager = new PluginManager(context);
}
}
}
return pluginManager;
}
private PluginManager(Context context){
this.context = context;
}
private DexClassLoader dexClassLoader;
private Resources resources;
/**
* (1.Activity.class, 2.layout)
* 加载插件
*/
public void loadPlugin(){
try{
File file = new File(Environment.getExternalStorageDirectory()+File.separator+"p.apk");
if (!file.exists()){
Log.e(TAG,"插件包不存在。。");
return;
}
//加载插件里面的class
String pluginPath = file.getAbsolutePath();
//dexClassLoader需要一个缓存目录 /data/data/当前应用的包名/pDir
File fileDir = context.getDir("pDir",Context.MODE_PRIVATE);
dexClassLoader = new DexClassLoader(pluginPath,fileDir.getAbsolutePath(),null,context.getClassLoader());
//下面是加载插件里面的layout
//加载资源
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPathMethod = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPathMethod.invoke(assetManager,pluginPath);
Resources r = context.getResources();
resources = new Resources(assetManager,r.getDisplayMetrics(),r.getConfiguration());
}catch (Exception e){
e.printStackTrace();
}
}
public DexClassLoader getDexClassLoader() {
return dexClassLoader;
}
public Resources getResources() {
return resources;
}
}
代理Activity,每次都是跳转到ProxyActivity,然后加载对应的插件中的Activity,所以ProxyActivity必须是Standard启动模式的
public class ProxyActivity extends Activity {
@Override
public Resources getResources() {
return PluginManager.getInstance(this).getResources();
}
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance(this).getDexClassLoader();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String className = getIntent().getStringExtra("className");
try {
Class<?> mPluginActivityClass = getClassLoader().loadClass(className);
//实例化插件里面的activity
Constructor<?> constructor = mPluginActivityClass.getConstructor(new Class[]{});
Object mPluginActivity = constructor.newInstance(new Object[]{});
ActivityInterface activityInterface = (ActivityInterface) mPluginActivity;
activityInterface.insertAppActivity(this);
Bundle bundle = new Bundle();
bundle.putString("appName","我是宿主传递过来的消息");
activityInterface.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
Intent proxyIntent = new Intent(this,ProxyActivity.class);
proxyIntent.putExtra("className",className);//包名+TestActivity
//要给TestActivity进栈
super.startActivity(proxyIntent);
}
}
plugin项目和宿主app都需要依赖standard库
最后打包apk,plugin项目打包apk,命名为p.apk,放在sdcard目录下。
安装宿主app项目即可,然后进行开始的操作即可。
说明:该项目只创建了activity的代理类,四大组件都是要创建对应的代理类,不然是启动不了的,所以相对来说比较繁琐
本文地址:https://blog.csdn.net/StudyOfAndroid/article/details/108117112