android 插件化原理及實現方式


一、插件化原理

  android插件化開發(把app當作插件一樣動態加載),主要需要解決兩個問題

  1、動態加載類

  2、動態加載資源

 

a、動態加載類

  類的加載主要靠ClassLoader這個類進行,classLoader體系圖如下

 

 

使用dexClassLoader進行加載apk文件即可實現動態加載類

 

具體代碼如下

 

 

private DexClassLoader createClassLoader(Context context,String apkPath) {
File dexOutputDir = context.getDir("dex", Context.MODE_PRIVATE);
String dexOutputPath = dexOutputDir.getAbsolutePath();

return new DexClassLoader(apkPath,dexOutputPath,mNativeLibDir,mContext.getClassLoader());
}

 

 

 進行構造dexclassLoader

DexClassLoader構造完成之后一般有兩種方式來將dex當作插件來使用

 

 

第一種方式:使用代理的方式,用代理組件的生命周期,代理插件的生命周期,將代理的 classLoader替換成插件的classLoader

 

當然也需要將宿主中的資源替換成插件中的資源

 

第二種方式:直接將插件里面的classLoader里面的 DexPathList里面的dexElements

 

 

 放到宿主的classLoader的DexPathList的dexElements前面,這樣就不需要代理直接能夠使用插件里面的類了,當然這樣也必須hook

ActivityManager,和 ActivityThread 里面的handler ,mH,繞過AMS的檢測,不過這樣做的代價就是hook比較多,系統源碼有修改就需要適配。下面是hook AMS的代碼,目的僅僅是騙過ams使插件不需要注冊也能起來,hook代碼如下

 

public class HookUtil {

public static void hookClassLoader(Context context,String apkPath) throws Exception{
String optimizedPath = context.getDir("plugins",Context.MODE_PRIVATE).getAbsolutePath();
String soLibStr = context.getDir("soLib",Context.MODE_PRIVATE).getAbsolutePath();
ClassLoader hostClassLoader = context.getClassLoader();
ClassLoader pluginClassLoader = new DexClassLoader(apkPath,optimizedPath,null,hostClassLoader);


Class<?> baseDexClassLoaderClazz = pluginClassLoader.getClass().getSuperclass();
Field pathListField = baseDexClassLoaderClazz.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pluginPathListObj = pathListField.get(pluginClassLoader);


Class<?> dexPathListClazz = pluginPathListObj.getClass();
Field dexElementsFiled = dexPathListClazz.getDeclaredField("dexElements");
dexElementsFiled.setAccessible(true);

Object pluginDexElement = dexElementsFiled.get(pluginPathListObj);

Object hostPathListObj = pathListField.get(hostClassLoader);
Object hostDexElement = dexElementsFiled.get(hostPathListObj);

int hostLength = Array.getLength(hostDexElement);
int pluginLength = Array.getLength(pluginDexElement);

Object newDexElements = Array.newInstance(hostDexElement.getClass().getComponentType(),hostLength + pluginLength);

System.arraycopy(pluginDexElement,0,newDexElements,0,pluginLength);
System.arraycopy(hostDexElement,0,newDexElements,pluginLength,hostLength);

dexElementsFiled.set(hostPathListObj,newDexElements);

}
@SuppressLint("PrivateApi")
public static void hookAMS(Context context) throws Exception{
Class<?> activityTaskManagerClazz = Class.forName("android.app.ActivityManagerNative");
Field iActivityTaskManagerSingletonFiled = activityTaskManagerClazz.getDeclaredField("gDefault");
iActivityTaskManagerSingletonFiled.setAccessible(true);

Object iActivityTaskManagerSingletonObj = iActivityTaskManagerSingletonFiled.get(null);

Class<?> singleTonClazz = Class.forName("android.util.Singleton");
Field instanceField = singleTonClazz.getDeclaredField("mInstance");
instanceField.setAccessible(true);
Object activityTaskManagerObj = instanceField.get(iActivityTaskManagerSingletonObj);

Class<?> iActivityTaskManagerInterface = Class.forName("android.app.IActivityManager");
instanceField.set(iActivityTaskManagerSingletonObj, Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityTaskManagerInterface},
(proxy, method, args) -> {
if(method.getName().equals("startActivity")){
for (int i = 0; i < args.length; i++) {
if(args[i] instanceof Intent){
Intent intent = new Intent(context,ProxyActivity.class);
intent.putExtra("ExtraIntent",(Intent)args[i]);
args[i] = intent;
break;
}
}
}
return method.invoke(activityTaskManagerObj,args);
}));
}

public static int EXECUTE_TRANSACTION = 159;
public static int LAUNCH_ACTIVITY = 100;
public static void hookHandler(Context context) throws Exception{

Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object activityThreadObj = sCurrentActivityThreadField.get(null);

Field mHField = activityThreadClazz.getDeclaredField("mH");
mHField.setAccessible(true);
Object mHObject = mHField.get(activityThreadObj);

Class<?> handlerClazz = Class.forName("android.os.Handler");
Field callBackField = handlerClazz.getDeclaredField("mCallback");
callBackField.setAccessible(true);
callBackField.set(mHObject,new Handler.Callback(){
@Override
public boolean handleMessage(@NonNull Message msg) {
try{
if(msg.what == LAUNCH_ACTIVITY){
Object activityRecordObject = msg.obj;
Class<?> activityRecordClass = activityRecordObject.getClass();
Field fieldIntent = activityRecordClass.getDeclaredField("intent");
fieldIntent.setAccessible(true);
Intent intent = (Intent) fieldIntent.get(activityRecordObject);
//將intent替換回去
Object extraIntent = intent.getParcelableExtra("ExtraIntent");
if(extraIntent != null){
fieldIntent.set(activityRecordObject,extraIntent);
}
}

if(msg.what == EXECUTE_TRANSACTION){
Object clientTransactionObj = msg.obj;
Class<?> clientTransactionClazz = Class.forName("android.app.servertransaction.ClientTransaction");
Field callbacksField = clientTransactionClazz.getDeclaredField("mActivityCallbacks");
Object callbacks = callbacksField.get(clientTransactionObj);

int length = Array.getLength(callbacks);

for (int i = 0;i<length;i++){
Object callback = Array.get(callbacks,i);
if(callback.getClass().getName().equals("android.app.servertransaction.LaunchActivityItem")){
Class<?> launchActivityItemClazz = Class.forName("android.app.servertransaction.LaunchActivityItem");
Field fieldIntent = launchActivityItemClazz.getDeclaredField("mIntent");
Intent intent = (Intent) fieldIntent.get(callback);
//將前面存的intent存回去
Object extraIntent = intent.getParcelableExtra("ExtraIntent");
if(extraIntent != null){
fieldIntent.set(callback,extraIntent);
}
break;
}
}
}
}catch (Exception e){
e.printStackTrace();
}
return false;
}
});
}}

b、動態加載資源

 

 

 

 

 

 通過反射構造apk路徑的assetManager,來構造Resource就能動態使用資源了。

 

 
        

 

 

 

 

 



 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM