插件化开发:
插件化开发已经是从热门开始走向衰落的地步了,但是其中的技术点还是很值的我们学习的,简单记录下学习之路。
插件化开发的好处:
1.不需要将apk安装到手机,下载就可以使用;
2.可以动态更新插件apk;
3.对于游戏sdk行业,插件化开发sdk可以减轻cp的接入与更新,通过动态或者逆向替换插件实现sdk的更新;
4.等等。
插件化开发的缺点:
1.开发难度大,适配各个版本,各种问题;
2.安卓高版本api的限制;
3.等等;
通过插桩式来实现加载插件
实现的效果:
要解决的问题:
- 我们的插件apk是没有安装到手机的,四大组件不具备上下文对象,没有生命周期,类没有被加载,资源没有被加载。
开发实现:
1.没有上下文,生命周期,通过宿主apk传递,通过代理模式,代理生命周期。首先实现一个Activity的代理对象。
public interface PluginActivityInterface {
public void attachActivity(Activity activity);
public void onCreate(Bundle savedInstanceState);
public void onResume();
public void onPause();
public void onStart();
public void onRestart();
public void onStop();
public void onDestroy();
public void onSaveInstanceState(Bundle outState);
public boolean onTouchEvent(MotionEvent event);
public void onBackPressed();
}
在插件中实现一个BaseActivity实现那这套接口,重写需要上下调用的方法,让他在宿主apk实现,代码中加了很多 plugActivity是否为null的情况是为了让插件apk也能够单独的运行。
public class BaseActivity extends Activity implements PluginActivityInterface {
Activity plugActivity;
public static final String TAG ="BaseActivity";
@Override
public void attachActivity(Activity activity) {
plugActivity = activity;
Log.d(TAG,"attachActivity plugActivity="+plugActivity);
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG,"onCreate");
if (plugActivity == null) {
super.onCreate(savedInstanceState);
}
}
@Override
public void setContentView(int layoutResID) {
if (plugActivity != null) {
plugActivity.setContentView(layoutResID);
}else {
super.setContentView(layoutResID);
}
}
@Override
public ClassLoader getClassLoader() {
if (plugActivity != null) {
return plugActivity.getClassLoader();
}
return super.getClassLoader();
}
@Override
public View findViewById(int id) {
if (plugActivity != null) {
return plugActivity.findViewById(id);
}
return super.findViewById(id);
}
@Override
public void startActivity(Intent intent) {
if (plugActivity != null){
plugActivity.startActivity(intent);
}else {
super.startActivity(intent);
}
}
@Override
public void setContentView(View view) {
if (plugActivity != null) {
plugActivity.setContentView(view);
}else {
super.setContentView(view);
}
}
@Override
public Window getWindow() {
if (plugActivity != null) {
return plugActivity.getWindow();
}
return super.getWindow();
}
@Override
public WindowManager getWindowManager() {
if (plugActivity != null) {
return plugActivity.getWindowManager();
}
return super.getWindowManager();
}
@Override
public Intent getIntent() {
if(plugActivity!=null){
return plugActivity.getIntent();
}
return super.getIntent();
}
@NonNull
@Override
public LayoutInflater getLayoutInflater() {
if(plugActivity!=null) {
return plugActivity.getLayoutInflater();
}
return super.getLayoutInflater();
}
@Override
public ApplicationInfo getApplicationInfo() {
if(plugActivity!=null) {
return plugActivity.getApplicationInfo();
}
return super.getApplicationInfo();
}
@Override
public void onResume() {
Log.d(TAG, "onResume");
if (plugActivity == null) {
super.onResume();
}
}
@Override
public void onPause() {
Log.d(TAG, "onPause");
if (plugActivity == null) {
super.onPause();
}
}
@Override
public void onStart() {
Log.d(TAG, "onStart");
if (plugActivity == null) {
super.onStart();
}
}
@Override
public void onRestart() {
Log.d(TAG, "onRestart");
if (plugActivity == null) {
super.onRestart();
}
}
@Override
public void onStop() {
Log.d(TAG, "onStop");
if (plugActivity == null) {
super.onStop();
}
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
if (plugActivity == null) {
super.onDestroy();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
}
}
然后让插件中的MainActivity去继承BaseActivity,
public class MainActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setOnclick();
}
private void setOnclick() {
View show = findViewById(R.id.btn_show);
View start = findViewById(R.id.btn_start);
show.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (plugActivity != null) {
Log.d("lkx_debug", "插件show");
Toast.makeText(plugActivity, "哈哈", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "哈哈", Toast.LENGTH_SHORT).show();
}
}
});
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (plugActivity != null) {
Log.d("lkx_debug", "插件start");
startActivity(new Intent(plugActivity, TopActivity.class));
} else {
startActivity(new Intent(MainActivity.this, TopActivity.class));
}
}
});
}
}
然后编译打包生成一个apk,我们将其拷贝到宿主的assets目录下
2.加载插件,根据插件路径,创建加载apk的ClassesLoader,和Resource对象
/**
* 加载插件
*/
public void loadPlugin(Context context, String plugPath) {
this.context = context;
this.plugPath = plugPath;
PackageManager packageManager = context.getPackageManager();
packageInfo = packageManager.getPackageArchiveInfo(plugPath, PackageManager.GET_ACTIVITIES);
File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
dexClassLoader = new DexClassLoader(plugPath, dexOutFile.getAbsolutePath()
, null, context.getClassLoader());
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, plugPath);
resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
3.在宿主apk中创建一个代理Activity
public class ProxyActivity extends AppCompatActivity {
private String activityName;
PluginActivityInterface pluginActivityInterface;
private static final String TAG = "ProxyActivity";
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityName = getIntent().getStringExtra("activity_name");
try {
Class activityClass = getClassLoader().loadClass(activityName);
Constructor constructor = activityClass.getConstructor(new Class[]{});
Object instance = constructor.newInstance(new Object[]{});
pluginActivityInterface = (PluginActivityInterface) instance;
pluginActivityInterface.attachActivity(this);
Bundle bundle = new Bundle();
pluginActivityInterface.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*ProxyActivity的启动模式必须为standard
* @param intent
*/
@Override
public void startActivity(Intent intent) {
Intent intent1 = new Intent(this,ProxyActivity.class);
intent1.putExtra("activity_name",intent.getComponent().getClassName());
Log.d("lkx_debug","intent.getComponent().getClassName()="+intent.getComponent().getClassName());
super.startActivity(intent1);
}
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance().getClassLoader();
}
@Override
public Resources getResources() {
return PluginManager.getInstance().getResources();
}
@Override
protected void onStart() {
super.onStart();
pluginActivityInterface.onStart();
}
@Override
protected void onDestroy() {
super.onDestroy();
pluginActivityInterface.onDestroy();
}
@Override
protected void onPause() {
super.onPause();
pluginActivityInterface.onPause();
}
@Override
protected void onResume() {
super.onResume();
pluginActivityInterface.onResume();
}
@Override
protected void onStop() {
super.onStop();
pluginActivityInterface.onStop();
}
@Override
protected void onRestart() {
super.onRestart();
pluginActivityInterface.onRestart();
}
}
当我们需要加载插件中的MainActivity的时候,我们通过新创建的ClassesLoader去反射加载插件中的MainActivity对象,将其转为PluginActivityInterface,然后在ProxyActivity的生命周期中调用对应的方法就让插件中的MainActivity获得了生命周期。setContentView调用的是ProxyActivity的方法,但是资源是用的插件中的资源。所以插件MainActivity就展示出来了。
完整代码:https://gitee.com/kabuda777/KaBuPluginOne.git