若是你还不知道什么叫插件化开发,那么你应该先读一读以前写的这篇博客:Android插件化开发,初入殿堂
java
上一篇博客主要从总体角度分析了一下Android插件化开发的几个难点与动态加载没有被安装的apk中的Activity和资源的方法。其实通常的插件开发主要也就是加载个Activity,读取一些资源图片之类的。可是总有遇到特殊状况的时候,好比加载Service。git
要动态加载Service,有两种思路:一是经过NDK的形式,将Service经过C++运行起来(这种方法我没有尝试,只听群里的朋友说实现过);另外一种就是我使用的,具体思路和上一篇中提到加载Activity的方法同样,使用托管所的形式,因为上一篇博客没有讲清楚,这里就详细讲一下经过托管所实现加载插件中Service的方法。github
如下几点是每个Android开发组确定都知到的: 一个apk若是没有被安装的话是没有办法直接运行的。一个JAVA类的class文件是能够经过classload类加载器读取的。一个apk实际上就是一个压缩包,其中包含了一个.dex文件就是咱们的代码文件。那么,接下来基本思路咱们就能够明确了:apk没办法直接运行,apk中有代码文件,代码文件能够被classload读取。app
在Android中有两种classload,分别是DexClassLoader、PathClassLoader。后者只能加载/data/app目录下的apk也就是apk必需要安装才能被加载,这不是咱们想要的,因此咱们使用前者:DexClassLoader。框架
public class CJClassLoader extends DexClassLoader { //建立一个插件加载器集合,对固定的dex使用固定的加载器能够防止多个加载器同时加载一个dex形成的错误。 private static final HashMap<String, CJClassLoader> pluginLoader = new HashMap<String, CJClassLoader>(); protected CJClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, optimizedDirectory, libraryPath, parent); } /** * 返回dexPath对应的加载器 */ public static CJClassLoader getClassLoader(String dexPath, Context cxt, ClassLoader parent) { CJClassLoader cjLoader = pluginLoader.get(dexPath); if (cjLoader == null) { // 获取到app的启动路径 final String dexOutputPath = cxt .getDir("dex", Context.MODE_PRIVATE).getAbsolutePath(); cjLoader = new CJClassLoader(dexPath, dexOutputPath, null, parent); pluginLoader.put(dexPath, cjLoader); } return cjLoader; } }
以上只是一个开始,接着咱们须要考虑一个问题,一个Service是有oncreate->onstart->ondestroy生命周期以及一些回调方法的,这些回调方法在咱们正常使用的时候是由父类们(包括has...a...关系)或者说是SDK管理的,那么当咱们经过类加载器加载的时候,它是没有可以管理的父类的,也就是说咱们须要本身模拟SDK去管理插件Service的回调函数。那么这个去管理插件Service的类,就是以前提到的托管所。ide
这里是我将Service中的回调方法抽出来写成的一个接口函数
public interface I_CJService { IBinder onBind(Intent intent); void onCreate(); int onStartCommand(Intent intent, int flags, int startId); void onDestroy(); void onConfigurationChanged(Configuration newConfig); void onLowMemory(); void onTrimMemory(int level); boolean onUnbind(Intent intent); void onRebind(Intent intent); void onTaskRemoved(Intent rootIntent); }
.this
//一个托管所类 class CJProxyService extends Service{ //采用包含关系 protected I_CJService mPluginService; // 插件Service对象 }
这里采用包含关系而不是采用继承(或者说实现一个接口)的方式,是因为咱们须要重写Service中的方法,而这些被重写的方法都须要用到接口对象相应的接口方法。
.net
public class CJProxyService extends Service{ @Override public void onConfigurationChanged(Configuration newConfig) { mPluginService.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig); } @Override public void onLowMemory() { mPluginService.onLowMemory(); super.onLowMemory(); } @Override @SuppressLint("NewApi") public void onTrimMemory(int level) { mPluginService.onTrimMemory(level); super.onTrimMemory(level); } @Override public boolean onUnbind(Intent intent) { mPluginService.onUnbind(intent); return super.onUnbind(intent); } @Override public void onRebind(Intent intent) { mPluginService.onRebind(intent); super.onRebind(intent); } }
看到这里你们应该也就明白了,托管所实际上就是一个普通的Service类,可是这个托管所是正常运行的,是由SDK管理回调函数的,咱们经过这个Service的回调函数去调用插件Service中相应的回调方法,就间接的管理了插件Service的生命周期(此处能够类比Activity与Fragment的关系)插件
到这里为止,咱们已经能够成功调起一个插件Service了,接下来的问题就是这个I_CJSrvice对象从哪里来?很简单,经过类加载器加载一个
private void init(Intent itFromApp) { Object instance = null; try { Class<?> serviceClass; if (CJConfig.DEF_STR.equals(mDexPath)) { serviceClass = super.getClassLoader().loadClass(mClass); } else { serviceClass = this.getClassLoader().loadClass(mClass); } Constructor<?> serviceConstructor = serviceClass .getConstructor(new Class[] {}); instance = serviceConstructor.newInstance(new Object[] {}); } catch (Exception e) { } setRemoteService(instance); mPluginService.setProxy(this, mDexPath); } /** * 保留一份插件Service对象 */ protected void setRemoteService(Object service) { if (service instanceof I_CJService) { mPluginService = (I_CJService) service; } else { throw new ClassCastException( "plugin service must implements I_CJService"); } }
这样就能够拿到一个I_CJSrvice对象mPluginService了,若是到此为止,仍是会有问题,由于此时mPluginService中例如onStart方法还对应的是那个插件中的onStart也就是父类的onStart(这里比较绕,我不知道该如何描述),而以前咱们又说过,经过反射加载的类是没有父类的,那么若是此时强制调用那个反射对象的@Override方法是会报空指针的,由于找不到父类。那么解决的办法就是再去插件Service中重写每一个@Override的方法。
//.......篇幅有限,部分截取 public abstract class CJService extends Service implements I_CJService { /** * that指针指向的是当前插件的Context(因为是插件化开发,this指针绝对不能使用) */ protected Service that; // 替代this指针 @Override public IBinder onBind(Intent intent) { if (mFrom == CJConfig.FROM_PLUGIN) { return null; } else { return that.onBind(intent); } } }
经过代能够看到:咱们使用了一个that对象来替代本来的this对象,而后咱们只须要经过在托管所中将这个that对象赋值为托管所的this对象,也就是插件中的全部that.xxx都至关于调用的是托管所的this.xxx,那么动态替换的目的就达到了,这样咱们也就成功的加载了一个未被安装的插件apk中的Service。
有关本类中的代码,以及完整的Demo,你能够关注:Android插件式开发框架 CJFrameForAndroid