1、介绍
我们知道PathClassLoader是一个应用的默认加载器(而且他只能加载data/app/xxx.apk的文件),但是我们加载插件一般使用DexClassLoader加载器,所以这里就有问题了,其实如果对于开始的时候,每个人都会认为很简单,很容易想到使用DexClassLoader来加载Activity获取到class对象,在使用Intent启动
2、替换LoadApk里面的mClassLoader
我们知道我们可以将我们使用的DexClassLoader加载器绑定到系统加载Activity的类加载器上就可以了,这个是我们的思路。也是最重要的突破点。下面我们就来通过源码看看如何找到加载Activity的类加载器。加载Activity的时候,有一个很重要的类:LoadedApk.Java,这个类是负责加载一个Apk程序的,我们可以看一下他的源码:
我们知道内部有个mClassLoader成员变量,我们只需要获取它就可以了,因为它不是静态的,所以我们需要先获取LoadApk这个类的对象,我们再去
看看ActivityThread.java这个类
我们可以发现ActivityThread里面有个静态的成员变量sCurrentActivityThread,然后还有一个ArrayMap存放Apk包名和LoadedApk映射关系的数据结构,我们通过反射来获取mClassLoader对象。
如果对ActivityThread.java这个类不熟悉的可以看我这篇博客,http://blog.csdn.net/u011068702/article/details/53207039 (Android插件化开发之AMS与应用程序(客户端ActivityThread、Instrumentation、Activity)通信模型分析)非常重要,是app程序的入口处。
3、实现具体代码
1)我们先需要一个测试apk,然后把这个测试的test.apk,放到手机sdcard里面去,关键代码如下
package com.example.testapkdemo; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Toast; public class MainActivity extends ActionBarActivity { public static View parentView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (parentView == null) { setContentView(R.layout.activity_main); } else { setContentView(parentView); } findViewById(R.id.button).setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "我是来自插件", Toast.LENGTH_SHORT).show(); } }); } public void setView(View view) { this.parentView = view; } }
接下来是我宿主代码:
ReflectHelper.java 这个是的我的反射帮助类
package com.example.dexclassloaderactivity; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Arrays; /** * 反射辅助函数 * @author * */ public class ReflectHelper { public static final Class<?>[] PARAM_TYPE_VOID = new Class<?>[]{}; public static Object invokeStaticMethod(String className, String methodName, Class<?>[] paramTypes, Object...params) { try { Class<?> clazz = Class.forName(className); Method method = clazz.getMethod(methodName, paramTypes); method.setAccessible(true); return method.invoke(null, params); } catch (Exception e) { e.printStackTrace(); } return null; } public static Object invokeMethod(String className, String methodName, Class<?>[] paramTypes, Object obj, Object...params) { try { Class<?> clazz = Class.forName(className); Method method = clazz.getMethod(methodName, paramTypes); method.setAccessible(true); return method.invoke(obj, params); } catch (Exception e) { e.printStackTrace(); } return null; } public static Object getStaticField(String className, String fieldName) { try { Class<?> clazz = Class.forName(className); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); return field.get(null); } catch (Exception e) { e.printStackTrace(); } return null; } public static Object getField(String className, String fieldName, Object obj) { try { Class<?> clazz = Class.forName(className); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); return field.get(obj); } catch (Exception e) { e.printStackTrace(); } return null; } public static void setStaticField(String className, String fieldName, Object value) { try { Class<?> clazz = Class.forName(className); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(null, value); } catch (Exception e) { e.printStackTrace(); } } public static void setField(String className, String fieldName, Object obj, Object value) { try { Class<?> clazz = Class.forName(className); Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } catch (Exception e) { e.printStackTrace(); } } public static Object createInstance(String className, Class<?>[] paramTypes, Object...params) { Object object = null; try { Class<?> cls = Class.forName(className); Constructor<?> constructor = cls.getConstructor(paramTypes); constructor.setAccessible(true); object = constructor.newInstance(params); }catch (Exception e) { e.printStackTrace(); } return object; } /** * Locates a given field anywhere in the class inheritance hierarchy. * * @param instance an object to search the field into. * @param name field name * @return a field object * @throws NoSuchFieldException if the field cannot be located */ public static Field findField(Object instance, String name) throws NoSuchFieldException { for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { try { Field field = clazz.getDeclaredField(name); if (!field.isAccessible()) { field.setAccessible(true); } return field; } catch (NoSuchFieldException e) { // ignore and search next } } throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass()); } /** * Locates a given field anywhere in the class inheritance hierarchy. * * @param cls to search the field into. * @param name field name * @return a field object * @throws NoSuchFieldException if the field cannot be located */ public static Field findField2(Class<?> cls, String name) throws NoSuchFieldException { Class<?> clazz = null; for (clazz = cls; clazz != null; clazz = clazz.getSuperclass()) { try { Field field = clazz.getDeclaredField(name); if (!field.isAccessible()) { field.setAccessible(true); } return field; } catch (NoSuchFieldException e) { // ignore and search next } } throw new NoSuchFieldException("Field " + name + " not found in " + clazz); } /** * Locates a given method anywhere in the class inheritance hierarchy. * * @param instance an object to search the method into. * @param name method name * @param parameterTypes method parameter types * @return a method object * @throws NoSuchMethodException if the method cannot be located */ public static Method findMethod(Object instance, String name, Class<?>... parameterTypes) throws NoSuchMethodException { for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) { try { Method method = clazz.getDeclaredMethod(name, parameterTypes); if (!method.isAccessible()) { method.setAccessible(true); } return method; } catch (NoSuchMethodException e) { // ignore and search next } } throw new NoSuchMethodException("Method " + name + " with parameters " + Arrays.asList(parameterTypes) + " not found in " + instance.getClass()); } }
MyApplication.java 这个类用实现替换mClassLoader
package com.example.dexclassloaderactivity; import java.io.File; import java.lang.ref.WeakReference; import dalvik.system.DexClassLoader; import android.annotation.SuppressLint; import android.app.Application; import android.os.Environment; import android.util.ArrayMap; import android.util.Log; public class MyApplication extends Application{ public static final String TAG = "MyApplication"; public static final String AppName = "test.apk"; public static int i = 0; public static DexClassLoader mClassLoader; @Override public void onCreate() { Log.d(TAG, "替换之前系统的classLoader"); showClassLoader(); try { String cachePath = this.getCacheDir().getAbsolutePath(); String apkPath = /*Environment.getExternalStorageState() + File.separator*/"/sdcard/"+ AppName; mClassLoader = new DexClassLoader(apkPath, cachePath,cachePath, getClassLoader()); loadApkClassLoader(mClassLoader); } catch (Exception e) { e.printStackTrace(); } Log.d(TAG, "替换之后系统的classLoader"); showClassLoader(); } @SuppressLint("NewApi") public void loadApkClassLoader(DexClassLoader loader) { try { Object currentActivityThread = ReflectHelper.invokeMethod("android.app.ActivityThread", "currentActivityThread", new Class[] {},new Object[] {}); String packageName = this.getPackageName(); ArrayMap mpackages = (ArrayMap) ReflectHelper.getField("android.app.ActivityThread", "mPackages", currentActivityThread); WeakReference wr= (WeakReference)mpackages.get(packageName); Log.e(TAG, "mClassLoader:" + wr.get()); ReflectHelper.setField("android.app.LoadedApk", "mClassLoader", wr.get(), loader); Log.e(TAG, "load:" + loader); } catch (Exception e) { Log.e(TAG, "load apk classloader error:" + Log.getStackTraceString(e)); } } /** * 打印系统的classLoader */ public void showClassLoader() { ClassLoader classLoader = getClassLoader(); if (classLoader != null){ Log.i(TAG, "[onCreate] classLoader " + i + " : " + classLoader.toString()); while (classLoader.getParent()!=null){ classLoader = classLoader.getParent(); Log.i(TAG,"[onCreate] classLoader " + i + " : " + classLoader.toString()); i++; } } } }
然后就是MainActivity.java文件,里面包含了下面另外一种方式,打开activity,所以我把函数 inject(DexClassLoader loader)先注释掉
package com.example.dexclassloaderactivity; import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Field; import android.content.Intent; import android.os.Bundle; import android.os.Environment; import android.support.v7.app.ActionBarActivity; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; import dalvik.system.DexClassLoader; import dalvik.system.PathClassLoader; public class MainActivity extends ActionBarActivity{ public static final String TAG = "MainActivity"; public static final String AppName = "test.apk"; public static DexClassLoader mDexClassLoader = null; public static final String APPName = "test.apk"; public TextView mText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.text2).setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { try { // inject(MyApplication.mClassLoader); String apkPath = Environment.getExternalStorageDirectory().toString() + File.separator + APPName; Class clazz = MyApplication.mClassLoader.loadClass("com.example.testapkdemo.MainActivity"); Intent intent = new Intent(MainActivity.this, clazz); startActivity(intent); finish(); } catch (Exception e) { Log.e(TAG, "name:" + Log.getStackTraceString(e)); } } }); } private void inject(DexClassLoader loader){ PathClassLoader pathLoader = (PathClassLoader) getClassLoader(); try { Object dexElements = combineArray( getDexElements(getPathList(pathLoader)), getDexElements(getPathList(loader))); Object pathList = getPathList(pathLoader); setField(pathList, pathList.getClass(), "dexElements", dexElements); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } private static Object getPathList(Object baseDexClassLoader) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException { ClassLoader bc = (ClassLoader)baseDexClassLoader; return getField(baseDexClassLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList"); } private static Object getField(Object obj, Class<?> cl, String field) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field localField = cl.getDeclaredField(field); localField.setAccessible(true); return localField.get(obj); } private static Object getDexElements(Object paramObject) throws IllegalArgumentException, NoSuchFieldException, IllegalAccessException { return getField(paramObject, paramObject.getClass(), "dexElements"); } private static void setField(Object obj, Class<?> cl, String field, Object value) throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field localField = cl.getDeclaredField(field); localField.setAccessible(true); localField.set(obj, value); } private static Object combineArray(Object arrayLhs, Object arrayRhs) { Class<?> localClass = arrayLhs.getClass().getComponentType(); int i = Array.getLength(arrayLhs); int j = i + Array.getLength(arrayRhs); Object result = Array.newInstance(localClass, j); for (int k = 0; k < j; ++k) { if (k < i) { Array.set(result, k, Array.get(arrayLhs, k)); } else { Array.set(result, k, Array.get(arrayRhs, k - i)); } } return result; } }
这里一定要注意,我们犯了3个错,
1、test.apk的路径写错了,下次写文件路径的时候,我们应该需要加上File file = new File(path); 用file.exist()函数来判断是否存在
2、从sdcard卡里面读取test.apk,没加上权限, <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
3、写了Application,忘了在AndroidManifest.xml文件里面声明。
我们还需要注意要加上开启activity 在AndroidManifest.xml里面注册,切记,希望下次不要再次犯错。
AndroidManifest.xml文件如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.dexclassloaderactivity" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="21" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <application android:name="com.example.dexclassloaderactivity.MyApplication" android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="com.example.testapkdemo.MainActivity"> </activity> </application> </manifest>
运行结果如下: