Android插件化开发---运行未安装apk中的Service

http://blog.csdn.net/kymjs/article/details/40677357

如果你还不知道什么叫插件化开发,那么你应该先读一读之前写的这篇博客:Android插件化开发,初入殿堂

        上一篇博客主要从整体角度分析了一下Android插件化开发的几个难点与动态加载没有被安装的apk中的Activity和资源的方法。其实一般的插件开发主要也就是加载个Activity,读取一些资源图片之类的。但是总有遇到特殊情况的时候,比如加载Service。

        要动态加载Service,有两种思路:一是通过NDK的形式,将Service通过C++运行起来(这种方法我没有尝试,只听群里的朋友说实现过);另一种就是我使用的,具体思路和上一篇中提到加载Activity的方法一样,使用托管所的形式,由于上一篇博客没有讲清楚,这里就详细讲一下通过托管所实现加载插件中Service的方法。

        以下几点是每一个Android开发组肯定都知到的: 一个apk如果没有被安装的话是没有办法直接运行的。一个JAVA类的class文件是可以通过classload类加载器读取的。一个apk实际上就是一个压缩包,其中包含了一个.dex文件就是我们的代码文件。那么,接下来基本思路我们就可以明确了:apk没办法直接运行,apk中有代码文件,代码文件可以被classload读取。

        在Android中有两种classload,分别是DexClassLoader、PathClassLoader。后者只能加载/data/app目录下的apk也就是apk必须要安装才能被加载,这不是我们想要的,所以我们使用前者:DexClassLoader。

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public class CJClassLoader extends DexClassLoader {  
  2.     //创建一个插件加载器集合,对固定的dex使用固定的加载器可以防止多个加载器同时加载一个dex造成的错误。  
  3.     private static final HashMap<String, CJClassLoader> pluginLoader = new HashMap<String, CJClassLoader>();  
  4.    
  5.     protected CJClassLoader(String dexPath, String optimizedDirectory,  
  6.             String libraryPath, ClassLoader parent) {  
  7.         super(dexPath, optimizedDirectory, libraryPath, parent);  
  8.     }  
  9.    
  10.     /** 
  11.      * 返回dexPath对应的加载器 
  12.      */  
  13.     public static CJClassLoader getClassLoader(String dexPath, Context cxt,  
  14.             ClassLoader parent) {  
  15.         CJClassLoader cjLoader = pluginLoader.get(dexPath);  
  16.         if (cjLoader == null) {  
  17.             // 获取到app的启动路径  
  18.             final String dexOutputPath = cxt  
  19.                     .getDir("dex", Context.MODE_PRIVATE).getAbsolutePath();  
  20.             cjLoader = new CJClassLoader(dexPath, dexOutputPath, null, parent);  
  21.             pluginLoader.put(dexPath, cjLoader);  
  22.         }  
  23.         return cjLoader;  
  24.     }  
  25. }  

以上只是一个开始,接着我们需要考虑一个问题,一个Service是有oncreate->onstart->ondestroy生命周期以及一些回调方法的,这些回调方法在我们正常使用的时候是由父类们(包括has...a...关系)或者说是SDK管理的,那么当我们通过类加载器加载的时候,它是没有能够管理的父类的,也就是说我们需要自己模拟SDK去管理插件Service的回调函数。那么这个去管理插件Service的类,就是之前提到的托管所。

这里是我将Service中的回调方法抽出来写成的一个接口

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public interface I_CJService {  
  2.     IBinder onBind(Intent intent);  
  3.    
  4.     void onCreate();  
  5.    
  6.     int onStartCommand(Intent intent, int flags, int startId);  
  7.    
  8.     void onDestroy();  
  9.    
  10.     void onConfigurationChanged(Configuration newConfig);  
  11.    
  12.     void onLowMemory();  
  13.    
  14.     void onTrimMemory(int level);  
  15.    
  16.     boolean onUnbind(Intent intent);  
  17.    
  18.     void onRebind(Intent intent);  
  19.    
  20.     void onTaskRemoved(Intent rootIntent);  
  21. }  

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. //一个托管所类  
  2. class CJProxyService extends Service{  
  3.     //采用包含关系  
  4.     protected I_CJService mPluginService; // 插件Service对象  
  5. }  

这里采用包含关系而不是采用继承(或者说实现一个接口)的方式,

是由于我们需要重写Service中的方法,而这些被重写的方法都需要用到接口对象相应的接口方法。

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. public class CJProxyService extends Service{      
  2.     @Override  
  3.     public void onConfigurationChanged(Configuration newConfig) {  
  4.         mPluginService.onConfigurationChanged(newConfig);  
  5.         super.onConfigurationChanged(newConfig);  
  6.     }  
  7.    
  8.     @Override  
  9.     public void onLowMemory() {  
  10.         mPluginService.onLowMemory();  
  11.         super.onLowMemory();  
  12.     }  
  13.    
  14.     @Override  
  15.     @SuppressLint("NewApi")  
  16.     public void onTrimMemory(int level) {  
  17.         mPluginService.onTrimMemory(level);  
  18.         super.onTrimMemory(level);  
  19.     }  
  20.    
  21.     @Override  
  22.     public boolean onUnbind(Intent intent) {  
  23.         mPluginService.onUnbind(intent);  
  24.         return super.onUnbind(intent);  
  25.     }  
  26.    
  27.     @Override  
  28.     public void onRebind(Intent intent) {  
  29.         mPluginService.onRebind(intent);  
  30.         super.onRebind(intent);  
  31.     }  
  32. }  

看到这里大家应该也就明白了,托管所实际上就是一个普通的Service类,但是这个托管所是正常运行的,是由SDK管理回调函数的,我们通过这个Service的回调函数去调用插件Service中相应的回调方法,就间接的管理了插件Service的生命周期(此处可以类比Activity与Fragment的关系)

到这里为止,我们已经可以成功调起一个插件Service了,接下来的问题就是这个I_CJSrvice对象从哪里来?很简单,通过类加载器加载一个

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. private void init(Intent itFromApp) {  
  2.    
  3.         Object instance = null;  
  4.         try {  
  5.             Class<?> serviceClass;  
  6.             if (CJConfig.DEF_STR.equals(mDexPath)) {  
  7.                 serviceClass = super.getClassLoader().loadClass(mClass);  
  8.             } else {  
  9.                 serviceClass = this.getClassLoader().loadClass(mClass);  
  10.             }  
  11.             Constructor<?> serviceConstructor = serviceClass  
  12.                     .getConstructor(new Class[] {});  
  13.             instance = serviceConstructor.newInstance(new Object[] {});  
  14.         } catch (Exception e) {  
  15.         }  
  16.         setRemoteService(instance);  
  17.         mPluginService.setProxy(this, mDexPath);  
  18.     }  
  19.    
  20.     /** 
  21.      * 保留一份插件Service对象 
  22.      */  
  23.     protected void setRemoteService(Object service) {  
  24.         if (service instanceof I_CJService) {  
  25.             mPluginService = (I_CJService) service;  
  26.         } else {  
  27.             throw new ClassCastException(  
  28.                     "plugin service must implements I_CJService");  
  29.         }  
  30.     }  

这样就可以拿到一个I_CJSrvice对象mPluginService了,如果到此为止,还是会有问题,因为此时mPluginService中例如onStart方法还对应的是那个插件中的onStart也就是父类的onStart(这里比较绕,我不知道该如何描述),而之前我们又说过,通过反射加载的类是没有父类的,那么如果此时强制调用那个反射对象的@Override方法是会报空指针的,因为找不到父类。那么解决的办法就是再去插件Service中重写每个@Override的方法。

[java]  view plain copy print ? 在CODE上查看代码片 派生到我的代码片
  1. //.......篇幅有限,部分截取  
  2. public abstract class CJService extends Service implements I_CJService {  
  3.     /** 
  4.      * that指针指向的是当前插件的Context(由于是插件化开发,this指针绝对不能使用) 
  5.      */  
  6.     protected Service that; // 替代this指针  
  7.    
  8.     @Override  
  9.     public IBinder onBind(Intent intent) {  
  10.         if (mFrom == CJConfig.FROM_PLUGIN) {  
  11.             return null;  
  12.         } else {  
  13.             return that.onBind(intent);  
  14.         }  
  15.     }  
  16. }  

通过代可以看到:我们使用了一个that对象来替代原本的this对象,然后我们只需要通过在托管所中将这个that对象赋值为托管所的this对象,也就是插件中的所有that.xxx都相当于调用的是托管所的this.xxx,那么动态替换的目的就达到了,这样我们也就成功的加载了一个未被安装的插件apk中的Service。

    有关本类中的代码,以及完整的Demo,你可以关注: Android插件式开发框架 CJFrameForAndroid
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值