如果你还不知道甚么叫插件化开发,那末你应当先读1读之前写的这篇博客:Android插件化开发,初入殿堂
上1篇博客主要从整体角度分析了1下Android插件化开发的几个难点与动态加载没有被安装的apk中的Activity和资源的方法。其实1般的插件开发主要也就是加载个Activity,读取1些资源图片之类的。但是总有遇到特殊情况的时候,比如加载Service。
要动态加载Service,有两种思路:1是通过NDK的情势,将Service通过C++运行起来(这类方法我没有尝试,只听群里的朋友说实现过);另外一种就是我使用的,具体思路和上1篇中提到加载Activity的方法1样,使用托管所的情势,由于上1篇博客没有讲清楚,这里就详细讲1下通过托管所实现加载插件中Service的方法。
以下几点是每个Android开发组肯定都知到的: 1个apk如果没有被安装的话是没有办法直接运行的。1个JAVA类的class文件是可以通过classload类加载器读取的。1个apk实际上就是1个紧缩包,其中包括了1个.dex文件就是我们的代码文件。那末,接下来基本思路我们就能够明确了:apk没办法直接运行,apk中有代码文件,代码文件可以被classload读取。
在Android中有两种classload,分别是DexClassLoader、PathClassLoader。后者只能加载/data/app目录下的apk也就是apk必须要安装才能被加载,这不是我们想要的,所以我们使用前者:DexClassLoader。
public class CJClassLoader extends DexClassLoader {
//创建1个插件加载器集合,对固定的dex使用固定的加载器可以避免多个加载器同时加载1个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;
}
}
以上只是1个开始,接着我们需要斟酌1个问题,1个Service是有oncreate->onstart->ondestroy生命周期和1些回调方法的,这些回调方法在我们正常使用的时候是由父类们(包括has...a...关系)或说是SDK管理的,那末当我们通过类加载器加载的时候,它是没有能够管理的父类的,也就是说我们需要自己摹拟SDK去管理插件Service的回调函数。那末这个去管理插件Service的类,就是之条件到的托管所。
这里是我将Service中的回调方法抽出来写成的1个接口
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);
}
//1个托管所类
class CJProxyService extends Service{
//采取包括关系
protected I_CJService mPluginService; // 插件Service对象
}
这里采取包括关系而不是采取继承(或说实现1个接口)的方式,
是由于我们需要重写Service中的方法,而这些被重写的方法都需要用到接口对象相应的接口方法。
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);
}
}
看到这里大家应当也就明白了,托管所实际上就是1个普通的Service类,但是这个托管所是正常运行的,是由SDK管理回调函数的,我们通过这个Service的回调函数去调用插件Service中相应的回调方法,就间接的管理了插件Service的生命周期(此处可以类比Activity与Fragment的关系)
到这里为止,我们已可以成功调起1个插件Service了,接下来的问题就是这个I_CJSrvice对象从哪里来?很简单,通过类加载器加载1个
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);
}
/**
* 保存1份插件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");
}
}
这样就能够拿到1个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);
}
}
}
通过代可以看到:我们使用了1个that对象来替换本来的this对象,然后我们只需要通过在托管所中将这个that对象赋值为托管所的this对象,也就是插件中的所有that.xxx都相当于调用的是托管所的this.xxx,那末动态替换的目的就到达了,这样我们也就成功的加载了1个未被安装的插件apk中的Service。
有关本类中的代码,和完全的Demo,你可以关注:Android插件式开发框架 CJFrameForAndroid