一、插件化原理
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就能动态使用资源了。