Android插件化技术之旅 1 开篇 – 实现启动插件与调用插件中的Activity和Service
前言
Android技术如今已很成熟了,组件化、插件化、热修复等等框架层出不穷,假如只停留在单纯的会用框架上,技术永远得不到成长,只有懂得其原理,能够婉婉道来,能够自己手动写出,技术才会得到成长,与其焦虑未来,不如把握现在。本篇将手写教大家写出插件化框架,插件化技术是Android高级工程师必备的技术之一,懂其思想,知其原理。本篇专题将由10篇文章来详细的讲解插件化技术,深耕一个技术领域,才能懂得如何更广阔的横向发展。
本专题代码地址
什么是插件化?
插件化技术的起源于免安装运行APK的想法,这个免安装的APK就是一个插件,支持插件化的app可以在运行时加载和运行插件,这样便可以将app中少量不常用的功能板块做成插件,一方面减小了安装包的大小,另一方面可以实现app功能的动态扩展。
在过去几年有一款叫23code的应用,不知大家能否知道,这款应用大火在于,它可以直接下载demo并运行起来,这可以说是最早的插件化应用。
现在并没有什么新的插件化项目了,比较流行的框架有如下:
第一代:dynamic-load-apk最早使用ProxyActivity这种静态代理商技术,由ProxyActivity去控制插件中PluginActivity的生命周期。该种方式缺点显著,插件中的activity必需继承PluginActivity,开发时要小心解决context。而DroidPlugin通过Hook系统服务的方式启动插件中的Activity,使得开发插件的过程和开发普通的app没有什么区别,但是因为hook过多系统服务,异常复杂且不够稳固。
第二代:为了同时达到插件开发的低侵入性(像开发普通app一样开发插件)和框架的稳固性,在实现原理上都是趋近于选择尽量少的hook,并通过在manifest中预埋少量组件实现对四大组件的插件化。另外各个框架根据其设计思想都做了不同程度的扩展,其中Small更是做成了一个跨平台,组件化的开发框架。
第三代:VirtualApp比较厉害,能够完全模拟app的运行环境,能够实现app的免安装运行和双开技术。Atlas是阿里今年开源出来的一个结合组件化和热修复技术的一个app基础框架,其广泛的应用与阿里系的各个app,其号称是一个容器化框架。
插件化的未来:去年比较火的ReactNative,虽然它不可能成为最终方案,但是移动应用Web化是一个必然的趋势,就如同曾经的桌面应用由C/S到B/S的转变。从Google今年推崇的Flutter,移动应用越来越趋向于Web化。
插件化与组件化的区别?
至于什么是组件化,大家可以看我写的关于组件化的专题
组件化是将各个板块分成独立的组件,每个组件都要打进apk包中。
而插件化各个组件都可以单独打成apk包,在需要时下载apk包,并加载运行。
插件化原理
1 设计接纳标准,符合这个标准的app都能接入进来,该标准跟生命周期相关。
2 设计好插件遵循这个标准,因为插件没有安装(上下文均不可用),需要给插件传递一个上下文。
3 用一个空壳Activity 放入插件内容。
4 反射得到Apk里面MainActivity的全类名。
5 加载
image.png
从上图可以看出,启动一个差价,核心代码就是ProxyActivity和PluginInterfaceActivity.下面我们着重讲解核心思想的代码。
代码其实很简单,启动插件的过程:
- 首先需要一个空壳的ProxyActivity来启动插件的Activity。
- ProxyActivity根据插件apk包的信息,拿到插件中的ClassLoader和Resource,而后通过反射并创立MainActivity 转换为PluginInterfaceActivity,并调用其onCreate方法。
- 插件调用的setContentView被重写了,会去调用ProxyActivity的setContentView,因为ProxyActivity的getClassLoader和gerResource被重写是插件中的resource,所以ProxyActivity的setContentView能够访问插件中的资源,findViewById同样也是调用ProxyActivity的。
- ProxyActivity中的其余生命周期回调函数中调用相应PluginActivity的生命周期。
image.png
直接上代码
下面代码定义了插件Activity必需要实现的一个接口,也可说定义的一个标准,因为插件并没有安装到手机上,无法拿到上下文,生命周期自然也无法调用,我们需要将宿主的一个空壳ProxyActivity,将生命周期传递给插件。插件需要用到上下文的方法都需要重写。
public interface PluginInterfaceActivity { /** * 上下文的传递 通过上下文来启动Activity * * @param activity */ void attach(Activity activity); //---------------------- 生命周期 传递到插件中 -------------------------// void onCreate(@Nullable Bundle savedInstanceState); void onStart(); void onResume(); void onPause(); void onStop(); void onRestart(); void onDestroy(); void onSaveInstanceState(Bundle outState); boolean onTouchEvent(MotionEvent event); void onBackPressed();}
重写必需要用到的方法,插件的Activity都需要继承BaseActivity
public class BaseActivity extends AppCompatActivity implements PluginInterfaceActivity { protected Activity mActivity; @Override public void attach(Activity proxyActivity) { this.mActivity = proxyActivity; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { } @Override public void onStart() { } @Override public void onResume() { } @Override public void onPause() { } @Override public void onStop() { } @Override public void onRestart() { } @Override public void onDestroy() { } @Override public void onSaveInstanceState(Bundle outState) { } //--------------------------凡事用到上下文的地方都需要重写,需要重写的方法-----------------------------// /** * 这里我们要使用宿主传递过来的上下文进行加载view * * @param view */ @Override public void setContentView(View view) { if (mActivity != null) { mActivity.setContentView(view); } else { super.setContentView(view); } } @Override public void setContentView(int layoutResID) { if (mActivity != null) { mActivity.setContentView(layoutResID); } else { super.setContentView(layoutResID); } } @Override public <T extends View> T findViewById(int id) { if (mActivity != null) { return mActivity.findViewById(id); } else { return super.findViewById(id); } } @Override public Intent getIntent() { if (mActivity != null) { return mActivity.getIntent(); } return super.getIntent(); } @Override public ClassLoader getClassLoader() { if (mActivity != null) { return mActivity.getClassLoader(); } return super.getClassLoader(); } @Override public LayoutInflater getLayoutInflater() { if (mActivity != null) { return mActivity.getLayoutInflater(); } return super.getLayoutInflater(); } @Override public ApplicationInfo getApplicationInfo() { if (mActivity != null) { return mActivity.getApplicationInfo(); } return super.getApplicationInfo(); } @Override public Window getWindow() { return mActivity.getWindow(); } @Override public WindowManager getWindowManager() { return mActivity.getWindowManager(); } @Override public void startActivity(Intent intent) { if (mActivity != null) { //插件的launchMode 只能够是标准的 Intent intent1 = new Intent(); intent1.putExtra("className", intent.getComponent().getClassName()); mActivity.startActivity(intent1); } else { super.startActivity(intent); } } @Override public ComponentName startService(Intent service) { if (mActivity != null) { Intent m = new Intent(); m.putExtra("serviceName", service.getComponent().getClassName()); return mActivity.startService(m); } else { return super.startService(service); } }}
下面来看宿主是如何启动插件的呢?
通过一个空壳的Activity
ProxyActivity代理商的方式最早是由dynamic-load-apk提出的,其思想很简单,在主工程中放一个ProxyActivy,启动插件中的Activity时会先启动ProxyActivity,在ProxyActivity中创立插件Activity,并同步生命周期。
public class ProxyActivity extends AppCompatActivity { //需要加载插件的全类名 private String className; private PluginInterfaceActivity pluginInterfaceActivity; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); className = getIntent().getStringExtra("className");// Class.forName() //插件并没有被安装到手机上的 try { //className 代表activity的全类名 Class activityClass = getClassLoader().loadClass(className); //调用构造函数 Constructor constructors = activityClass.getConstructor(new Class[]{}); //得到Activity对象 Object newInstance = constructors.newInstance(new Object[]{}); //最好不要反射 onCreate() //通过标准来 pluginInterfaceActivity = (PluginInterfaceActivity) newInstance; //注入上下文 pluginInterfaceActivity.attach(this); Bundle bundle = new Bundle();//将少量信息传递 bundle.putString("test", "我是宿主给你传递数据"); pluginInterfaceActivity.onCreate(bundle); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } @Override public void startActivity(Intent intent) { String className = intent.getStringExtra("className"); Intent intent1 = new Intent(this, ProxyActivity.class); intent1.putExtra("className", className); super.startActivity(intent1); } @Override public ComponentName startService(Intent service) { String serviceName = service.getStringExtra("serviceName"); Intent intent = new Intent(this, ProxyService.class); intent.putExtra("serviceName", serviceName); return super.startService(intent); } //对外 @Override public ClassLoader getClassLoader() { return PluginManager.getInstance().getClassLoader(); } @Override public Resources getResources() { return PluginManager.getInstance().getResources(); } @Override protected void onStart() { super.onStart(); pluginInterfaceActivity.onStart(); } @Override protected void onResume() { super.onResume(); pluginInterfaceActivity.onResume(); } @Override protected void onPause() { super.onPause(); pluginInterfaceActivity.onPause(); } @Override protected void onStop() { super.onStop(); pluginInterfaceActivity.onStop(); } @Override protected void onDestroy() { super.onDestroy(); pluginInterfaceActivity.onDestroy(); }}
启动插件的代码如下:
public void loadPlugin(View view) { loadDownPlugin(); Intent intent = new Intent(this, ProxyActivity.class); //得到全类名 intent.putExtra("className", PluginManager.getInstance().getPackageInfo().activities[0].name); startActivity(intent); }
其实,启动插件就是启动宿主的一个空壳的Activity,将这个空壳的Activity的上文和生命周期传递到插件的Activity。
这便是启动插件的核心代码:
//className 代表activity的全类名 Class activityClass = getClassLoader().loadClass(className); //调用构造函数 Constructor constructors = activityClass.getConstructor(new Class[]{}); //得到Activity对象 Object newInstance = constructors.newInstance(new Object[]{}); //最好不要反射 onCreate() //通过标准来 pluginInterfaceActivity = (PluginInterfaceActivity) newInstance; //注入上下文 pluginInterfaceActivity.attach(this); Bundle bundle = new Bundle();//将少量信息传递 bundle.putString("test", "我是宿主给你传递数据"); pluginInterfaceActivity.onCreate(bundle);
其实我们就是启动了宿主中的一个空壳Activity,而后加载插件APK包中的资源,并将生命周期传递,那么下面我们思考一个问题:
插件中的MainActivity调用插件中的OtherActivity,是如何调用的呢?startActivity需不需要重写?
答案是一定的,启用插件中的其余Activity,其实就是重新创立一个新的空壳的Activity。
//重写插件中的startActivity 将要启动的Activity的全类名传递给ProxyActivity @Override public void startActivity(Intent intent) { if (mActivity != null) { //插件的launchMode 只能够是标准的 Intent intent1 = new Intent(); intent1.putExtra("className", intent.getComponent().getClassName()); mActivity.startActivity(intent1); } else { super.startActivity(intent); } } //重写ProxyActivity的startActivity,跳转一个新的ProxyActivity,并跳转Activity的全类名传递过去,相当于重走了一边上面的过程 @Override public void startActivity(Intent intent) { String className = intent.getStringExtra("className"); Intent intent1 = new Intent(this, ProxyActivity.class); intent1.putExtra("className", className); super.startActivity(intent1); }
ProxyActivity代理商方式主要注意两点:
- ProxyActivity中需要重写getResouces,getAssets,getClassLoader方法返回插件的相应对象。生命周期函数以及和客户交互相关函数,如onResume,onStop,onBackPressedon,KeyUponWindow,FocusChanged等需要转发给插件。
- PluginActivity中所有调用context的相关的方法,如setContentView,getLayoutInflater,getSystemService等都需要调用ProxyActivity的相应方法。
调用插件中的Service
通过上述的讲解,我们知道了调用插件中的Activity,其实就是在宿主中创立一个空壳的Acitvity,而后加载插件中的资源,传递上下文。
那么调用插件中的Service呢?原理是一样的,原理是一样的还是在宿主中创立一个空壳的Service ProxyService,ProxyService 将生命周期传递给插件中的Service
自己可以去实现一下,这里我只把核心代码给出
public class ProxyService extends Service { private String serviceName; private PluginInterfaceService pluginInterfaceService; @Override public IBinder onBind(Intent intent) { return null; } private void init(Intent intent) { //开启某个服务的全类名 serviceName = intent.getStringExtra("serviceName"); //生成一个class DexClassLoader classLoader = PluginManager.getInstance().getClassLoader(); try { Class<?> aClass = classLoader.loadClass(serviceName); Constructor<?> constructor = aClass.getConstructor(new Class[]{}); //获取某个service对象 Object newInstance = constructor.newInstance(new Object[]{}); pluginInterfaceService = (PluginInterfaceService) newInstance; pluginInterfaceService.attach(this); Bundle bundle = new Bundle(); pluginInterfaceService.onCreate(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (pluginInterfaceService == null) { init(intent); } return pluginInterfaceService.onStartCommand(intent, flags, startId); } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); pluginInterfaceService.onStart(intent, startId); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); pluginInterfaceService.onConfigurationChanged(newConfig); } @Override public void onLowMemory() { super.onLowMemory(); pluginInterfaceService.onLowMemory(); } @Override public void onTrimMemory(int level) { super.onTrimMemory(level); pluginInterfaceService.onTrimMemory(level); } @Override public boolean onUnbind(Intent intent) { pluginInterfaceService.onUnbind(intent); return super.onUnbind(intent); } @Override public void onRebind(Intent intent) { super.onRebind(intent); pluginInterfaceService.onRebind(intent); } @Override public void onTaskRemoved(Intent rootIntent) { super.onTaskRemoved(rootIntent); pluginInterfaceService.onTaskRemoved(rootIntent); } @Override public void onDestroy() { super.onDestroy(); pluginInterfaceService.onDestroy(); }}
需要注意的是:ProxyActivity和ProxyService肯定要在宿主的 Manifest 中注册。
OK,这编文章的讲解是插件化最初的时候出现的设计原理,接下来会一步步深入讲解。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Android插件化技术之旅 1 开篇 – 实现启动插件与调用插件中的Activity和Service