Android插件化開發---運行未安裝apk中的Service
來源:程序員人生 發布時間:2014-11-15 00:28:15 閱讀次數:2835次
如果你還不知道甚么叫插件化開發,那末你應當先讀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
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈