1.占位式插件化,宿主启动插件activity流程
插件开发三大要素:宿主 标准,插件包
第一步创建宿主工程
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="loadPligin"
android:text="加载插件" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="startPluginActivity"
android:text="启动插件activity" />
</LinearLayout>
2.制定标准
创建插件工程(一定是工程不是library):
准备工作做完了,之后会很长废话也比较多,我尽量简洁多贴图少说废话
首先我们要把标准添加到宿主工程和插件工程里面
插件工程同上不在贴图,在宿主模块里面定义一个插件管理类
public class PluginManager {
private static final String TAG = PluginManager.class.getSimpleName();
private Context context;
private DexClassLoader dexClassLoader;
private static PluginManager pluginManager;
private Resources resources;
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;
}
public DexClassLoader getDexClassLoader() {
return dexClassLoader;
}
public Resources getResources() {
return resources;
}
/**
* 加载插件activity
*/
private void loadPlugin() {
try {
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
if (!file.exists()) {
Log.e(TAG, "文件不存在");
return;
}
//获取到文件的位置
String pluginPath = file.getAbsolutePath();
//加载插件里面的class dexClassLoader 需要一个缓存目录 /data/data/包名/pDir
File fileDir = context.getDir("pDir", Context.MODE_PRIVATE);
dexClassLoader = new DexClassLoader(pluginPath, fileDir.getAbsolutePath(), null, context.getClassLoader());
//加载插件里面的布局
AssetManager assetManager = AssetManager.class.newInstance();
//把插件包的路径添加进去
Method addAssetPathMethod = assetManager.getClass().getMethod("addAssetPath", String.class);
//执行此方法,参数为插件包的路径
addAssetPathMethod.invoke(assetManager, pluginPath);
//宿主的配置
Resources r = context.getResources();
//使用宿主的resource加载插件里面的资源resource
resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
}
管理类准备好了现在要准备标准了,在standard里面定义ActivityInterface(我太懒了所以标准接口里面就写了我用到的几个方法,可以多写一些)
public interface ActivityInterface {
/**
* 把宿主的(app)的环境给插件
* @param appActivity
*/
void insertAppContext(Activity appActivity);
void onCreate(Bundle savedInstanceState) ;
void onStart() ;
void onResume() ;
void onDestroy() ;
}
在插件模块里面定义基础类 在PluginActivity中用的所有有关activity的方法都要在基础类中重写(看不懂继续往下|)
public class BaseActivity extends AppCompatActivity implements ActivityInterface {
//宿主的环境
public Activity appActivity;
@Override
public void insertAppContext(Activity appActivity) {
this.appActivity = appActivity ;
}
/**
* 添加注释因为要实现的是接口里面的方法而不是重写AppCompatActivity的
* @param savedInstanceState
*/
@SuppressLint("MissingSuperCall")
@Override
public void onCreate(Bundle savedInstanceState) {
}
@SuppressLint("MissingSuperCall")
@Override
public void onStart() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onResume() {
}
@SuppressLint("MissingSuperCall")
@Override
public void onDestroy() {
}
@Override
public void setContentView(int sourceId) {
appActivity.setContentView(sourceId);
}
@Override
public View findViewById(int id) {
return appActivity.findViewById(id);
}
@Override
public void startActivity(Intent intent) {
Intent intentNew = new Intent();
intentNew.putExtra("className",intent.getComponent().getClassName());
appActivity.startActivity(intent);
}
}
PluginActivity继承基础类
public class PluginActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_plugin);
Toast.makeText(appActivity,"我是插件",Toast.LENGTH_LONG).show();
}
}
这里一定要是全路径
因为插件apk 没有组件环境所以当宿主activity调用插件的activity时需要在宿主工程里面定义一个代理activity作为媒介
/**
* 代理activity 占位插件里面的activity
**/
public class ProxyActivity extends ComponentActivity {
@Override
public Resources getResources() {
return PluginManager.getInstance(this).getResources();
}
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance(this).getDexClassLoader();
}
/**
* 代理类里面会加载插件正在的类
* @param savedInstanceState
*/
@Override
protected void onCreate(@Nullable 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.insertAppContext(this);
activityInterface.onCreate(savedInstanceState);
}catch (Exception e){
}
}
}
在宿主的mainActivity中加载插件
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void loadPligin(View view) {
PluginManager.getInstance(this).loadPlugin();
}
/**
* 9.0以上需要动态获取权限
*
* @param view
*/
public void startPluginActivity(View view) {
int hasWriteStoragePermission = ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (hasWriteStoragePermission == PackageManager.PERMISSION_GRANTED) {
instant();
} else {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
}
private void instant() {
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];
//占位 代理activtiy
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className", activityInfo.name);
startActivity(intent);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//用户同意,执行操作
instant();
} else {
//用户不同意,向用户展示该权限作用
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
new AlertDialog.Builder(this)
.setMessage(R.string.storage_permissions_remind)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
})
.setNegativeButton("Cancel", null)
.create()
.show();
}
}
}
}
}
插件工程new出apk
将生成的apk(我这边改了名字)放入手机内存卡
插件里面的activity跳转
在plugin 添加testActivity
proxyActivty中添加
@Override
public void startActivity(Intent intent) {
String className = intent.getStringExtra("className");
//要给TestActivity进栈
Intent proxyIntent = new Intent(this, ProxyActivity.class);
proxyIntent.putExtra("className", className);
super.startActivity(proxyIntent);
}
pluginActivty 正常跳转
findViewById(R.id.btn_start_activity).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(appActivity,TestActivity.class);
startActivity(intent);
}
});
因为插件和宿主都有修改所以要重新打包上传。