本文说的关于动态加载的内容与之前一篇Android动态加载之插件开发 有关系,本文要说的加载有两种:
这两种方式都由各自的优缺点。
技术介绍 使用反射机制修改类加载器来实现动态加载Activity 我们知道PathClassLoader是一个应用的默认加载器(而且它只能加载data/app/xxx.apk的文件),但是我们加载插件一般使用DexClassLoader加载器,所以开始的时候,每个人都会很容易想到使用DexClassLoader来加载Activity获取到class对象,再使用Intent启动。但是实际上并不是想象的这么简单。因为Android中的四大组件都有一个特点就是他们有自己的启动流程和生命周期,我们使用DexClassLoader加载进来的Activity是不会涉及到任何启动流程和生命周期的概念,说白了,他就是一个普普通通的类。所以启动肯定会出错。 解决这个问题有两种思路:思路1:替换LoadedApk中的mClassLoader 只要让加载进来的Activity有启动流程和生命周期就行了,所以这里需要看一下一个Activity的启动过程,当然不会详细介绍一个Activity启动过程的。可以将使用的DexClassLoader加载器绑定到系统加载Activity的类加载器上,这个是最重要的突破点。下面我们就来通过源码看看如何找到加载Activity的类加载器。在找关于一个Apk或是Activity相关信息的时候,特别是启动流程的时候,肯定先找ActivityThread类,这个类很重要,也是关键突破口,它内部有很多的信息。 看一下ActivityThread.java的源码。代码清单 /frameworks/base/core/java/android/app/ActivityThread.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ... final ArrayMap<String, BackupAgent> mBackupAgents = new ArrayMap<String, BackupAgent>();private static ActivityThread sCurrentActivityThread; Instrumentation mInstrumentation; String mInstrumentationAppDir = null ; String mInstrumentationAppLibraryDir = null ; String mInstrumentationAppPackage = null ; String mInstrumentedAppDir = null ; String mInstrumentedAppLibraryDir = null ; boolean mSystemThread = false ;boolean mJitEnabled = false ;final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<String, WeakReference<LoadedApk>>(); final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages = new ArrayMap<String, WeakReference<LoadedApk>>(); final ArrayList<ActivityClientRecord> mRelaunchingActivities = new ArrayList<ActivityClientRecord>(); Configuration mPendingConfiguration = null ; ...
我们看到ActivityThread类中有一个自己的static对象,然后还有一个ArrayMap存放Apk包名和LoadedApk映射关系的数据结构。 LoadedApk.java是加载Activity的时候一个很重要的类,这个类是负责加载一个Apk程序的,我们可以看一下它的源码:代码清单 /frameworks/base/core/java/android/app/LoadedApk.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public final class LoadedApk { private static final String TAG = "LoadedApk" ; private final ActivityThread mActivityThread; private final ApplicationInfo mApplicationInfo; final String mPackageName; private final String mAppDir; private final String mResDir; private final String[] mSharedLibraries; private final String mDataDir; private final String mLibDir; private final File mDataDirFile; private final ClassLoader mBaseClassLoader; private final boolean mSecurityViolation; private final boolean mIncludeCode; private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments(); Resources mResources; private ClassLoader mClassLoader; private Application mApplication; private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers = new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>(); private final ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mUnregisteredReceivers = new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>(); private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices = new ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>>(); private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices = new ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>>(); int mClientCount = 0 ; Application getApplication () { return mApplication; } ...
我们可以看到它内部有一个mClassLoader变量,就是负责加载一个Apk程序的,所以只要获取到这个类加载器就可以了。mClassLoader变量不是static的,所以我们还得获取一个LoadedApk对象。
关于ActivityThread类为何如此重要,我们可以看看它的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public static void main (String[] args) { SamplingProfilerIntegration.start(); CloseGuard.setEnabled(false ); Environment.initForCurrentUser(); EventLogger.setReporter(new EventLoggingReporter()); Security.addProvider(new AndroidKeyStoreProvider()); Process.setArgV0("<pre-initialized>" ); Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false ); if (sMainThreadHandler == null ) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); if (false ) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread" )); }
它有Java程序运行的入口main方法,所有的APP程序的执行入口就是这里。 回到主干上来,我们现在开始编写代码实现反射获取mClassLoader类,然后将其替换成我们的DexClassLoader类,看一下项目结构:
PluginActivity —— 插件项目
DynamicActivityForClassLoader —— 宿主项目
PluginActivity项目很简单,就一个Activity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.example.dynamicactivityapk;public class MainActivity extends Activity { private 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.btn).setOnClickListener(new OnClickListener() { @Override public void onClick (View arg0) { Toast.makeText(getApplicationContext(), "I came from plugin~~" , Toast.LENGTH_LONG).show(); }}); } public static void setLayoutView (View view) { parentView = view; } }
编译PluginActivity项目: 下面看一下宿主项目,宿主项目其实最大的功能就是加载上面的PluginActivity.apk,然后启动内部的MainActivity就可以了,这里的核心代码就是如何通过反射替换系统的mClassLoader类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @SuppressLint("NewApi") private void loadApkClassLoader (DexClassLoader dLoader) { try { Object currentActivityThread = RefInvoke.invokeStaticMethod( "android.app.ActivityThread" , "currentActivityThread" , new Class[] {}, new Object[] {}); String packageName = this .getPackageName(); ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( "android.app.ActivityThread" , currentActivityThread, "mPackages" ); WeakReference wr = (WeakReference) mPackages.get(packageName); RefInvoke.setFieldOjbect("android.app.LoadedApk" , "mClassLoader" , wr.get(), dLoader); Log.i("demo" , "classloader:" +dLoader); } catch (Exception e) { Log.i("demo" , "load apk classloader error:" +Log.getStackTraceString(e)); } }
参数dLoader是需要替换的DexClassLoader,从外部传递过来,然后进行替换。我们看看外部定义的DexClassLoader类:
1 2 3 4 5 6 7 String filesDir = this .getCacheDir().getAbsolutePath(); String libPath = filesDir + File.separator +"PluginActivity.apk" ; Log.i("inject" , "fileexist:" +new File(libPath).exists()); loadResources(libPath); DexClassLoader loader = new DexClassLoader(libPath, filesDir,filesDir, getClassLoader());
这里,需要注意的是,DexClassLoader的最后一个参数,是DexClassLoader的parent,这里需要设置成PathClassLoader类,因为我们上面虽然说是替换PathClassLoader为DexClassLoader。但是PathClassLoader是系统本身默认的类加载器(也就是mClassLoader变量的值,我们如果单独的将mClassLoader的值设置为DexClassLoader的话,就会出错的),所以一定要将DexClassLoader的父加载器设置成PathClassLoader,因为类加载器是符合双亲委派机制的。 运行程序之前要在宿主项目的AndroidManifest.xml中声明插件的MainActivity
1 2 3 <activity android:name ="com.example.dynamicactivityapk.MainActivity" > </activity >
下面运行一下这个程序,首先将PluginActivity.apk放到宿主项目的cache目录下:
1 $ adb push PluginActivity.apk /data/data/com.exmaple.testinjectdex/cache
再点击按钮就能看到效果了。
因为要加载插件中的资源,所以需要调用loadResources方法
在测试过程中,发现插件项目中setContentView方法没有效果了。所以要在插件项目中定义一个static的方法,用来提前设置视图的。
思路2:合并PathClassLoader和DexClassLoader中的dexElements数组 下面介绍另外一种设置类加载器的方式。 先来看一下PathClassLoader和DexClassLoader类加载器的父类BaseDexClassLoader的源码: (需要注意的是PathClassLoader和DexClassLoader类的父加载器是BootClassLoader,它们的父类是BaseDexClassLoader)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; public BaseDexClassLoader (String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super (parent); this .pathList = new DexPathList(this , dexPath, libraryPath, optimizedDirectory); }
这里有一个DexPathList对象,再来看一下DexPathList.java源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 final class DexPathList { private static final String DEX_SUFFIX = ".dex" ; private static final String JAR_SUFFIX = ".jar" ; private static final String ZIP_SUFFIX = ".zip" ; private static final String APK_SUFFIX = ".apk" ; private final ClassLoader definingContext; private final Element[] dexElements; private final File[] nativeLibraryDirectories;
首先看一下这个类的描述,还有一个Elements数组,这个变量是专门存放加载的dex文件的路径的。系统默认的类加载器是PathClassLoader,本身一个程序加载之后会释放一个dex出来,这时候会将dex路径放到里面,当然DexClassLoader也是一样的,那么我们会想到,我们可以将DexClassLoader中的dexElements和PathClassLoader中的dexElements进行合并,然后再设置给PathClassLoader中。我们来看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 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(bc, 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; }
再运行宿主项目,可以正常运行,效果和前面一样。 这两个思路的原理都是为了让我们动态加载进来的Activity能够具备正常的启动流程和生命周期。项目下载
使用静态代理的方式来动态加载Activity 原理: 这里的ProxyActivity就是代理对象,而每个插件Activity都是被代理对象,每个插件Activity对象中都会有一个代理Activity的对象引用,这个就是静态代理,原理就很简单了,就是插件Activity实际上不是真正意义上的Activity了,而是它们把所有Activity中的任务用代理对象Activity来执行了,所以我们只需要在宿主项目中声明一个ProxyActivity,然后用反射的方法设置到每个动态加载的Activity中去就可以了,比如:Plugin1中Activity执行setContentView方法的时候,其实就是执行ProxyActivity中的setContentView方法了。 因此,这种方式来加载Activity的话,其实真正意义上每个插件的Activity都不再是像方式一中的那样,没有声明周期,没有启动流程了,它们就是一个普通的Activity类,然后将其生命周期的所有任务都交给代理Activity去执行就可以了。 下面我们来看一下项目:
DynamicActivityForProxy —— 宿主项目
PluginActivity —— 插件项目
看一下插件项目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.example.dynamicactivity;public class BaseActivity extends Activity { protected Activity mProxyActivity; public void setProxy (Activity proxyActivity) { mProxyActivity = proxyActivity; } @Override protected void onCreate (Bundle savedInstanceState) { } @Override public void setContentView (int layoutResID) { if (mProxyActivity != null && mProxyActivity instanceof Activity) { mProxyActivity.setContentView(layoutResID); mProxyActivity.findViewById(R.id.btn).setOnClickListener( new OnClickListener() { @Override public void onClick (View v) { Toast.makeText(mProxyActivity, "我是插件,你是谁!" ,Toast.LENGTH_LONG).show(); } }); } } }
这里重写了setContentView的方法,同时有一个setProxy方法。 再来看一下MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class MainActivity extends BaseActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onDestroy () { Log.i("demo" , "onDestory" ); } @Override protected void onPause () { Log.i("demo" , "onPause" ); } @Override protected void onResume () { Log.i("demo" , "onResume" ); } @Override protected void onStart () { Log.i("demo" , "onStart" ); } @Override protected void onStop () { Log.i("demo" , "onStop" ); } }
这里打印一下生命周期中的每个方法,待会需要验证。 运行插件项目,得到一个PluginActivity.apk。 下面看一下宿主项目: 首先看一下重要的代理对象ProxyActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 package com.example.dynamic.activity;public class ProxyActivity extends BaseActivity { private Object pluginActivity; private Class<?> pluginClass; private HashMap<String, Method> methodMap = new HashMap<String, Method>(); @SuppressLint("NewApi") @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); try { DexClassLoader loader = initClassLoader(); pluginClass = loader.loadClass("com.example.dynamicactivity.MainActivity" ); Constructor<?> localConstructor = pluginClass.getConstructor(new Class[] {}); pluginActivity = localConstructor.newInstance(new Object[] {}); Method setProxy = pluginClass.getMethod("setProxy" ,new Class[] { Activity.class }); setProxy.setAccessible(true ); setProxy.invoke(pluginActivity, new Object[] { this }); initMethodMap(); Method onCreate = pluginClass.getDeclaredMethod("onCreate" , new Class[] { Bundle.class }); onCreate.setAccessible(true ); onCreate.invoke(pluginActivity, new Object[] { new Bundle() }); } catch (Exception e) { Log.i("demo" , "load activity error:" + Log.getStackTraceString(e)); } } private void initMethodMap () { methodMap.put("onPause" , null ); methodMap.put("onResume" , null ); methodMap.put("onStart" , null ); methodMap.put("onStop" , null ); methodMap.put("onDestroy" , null ); for (String key : methodMap.keySet()){ try { Method method = pluginClass.getDeclaredMethod(key); method.setAccessible(true ); methodMap.put(key, method); }catch (Exception e){ Log.i("demo" , "get method error:" + Log.getStackTraceString(e)); } } } @SuppressLint("NewApi") private DexClassLoader initClassLoader () { String filesDir = this .getCacheDir().getAbsolutePath(); String libPath = filesDir + File.separator + "PluginActivity.apk" ; Log.i("inject" , "fileexist:" +new File(libPath).exists()); loadResources(libPath); DexClassLoader loader = new DexClassLoader(libPath, filesDir, null , getClass().getClassLoader()); return loader; } @Override protected void onDestroy () { super .onDestroy(); Log.i("demo" , "proxy onDestroy" ); try { methodMap.get("onDestroy" ).invoke(pluginActivity, new Object[]{}); }catch (Exception e){ Log.i("demo" , "run destroy error:" + Log.getStackTraceString(e)); } } @Override protected void onPause () { super .onPause(); Log.i("demo" , "proxy onPause" ); try { methodMap.get("onPause" ).invoke(pluginActivity, new Object[]{}); }catch (Exception e){ Log.i("demo" , "run pause error:" + Log.getStackTraceString(e)); } } @Override protected void onResume () { super .onResume(); Log.i("demo" , "proxy onResume" ); try { methodMap.get("onResume" ).invoke(pluginActivity, new Object[]{}); }catch (Exception e){ Log.i("demo" , "run resume error:" + Log.getStackTraceString(e)); } } @Override protected void onStart () { super .onStart(); Log.i("demo" , "proxy onStart" ); try { methodMap.get("onStart" ).invoke(pluginActivity, new Object[]{}); }catch (Exception e){ Log.i("demo" , "run start error:" + Log.getStackTraceString(e)); } } @Override protected void onStop () { super .onStop(); Log.i("demo" , "proxy onStop" ); try { methodMap.get("onStop" ).invoke(pluginActivity, new Object[]{}); }catch (Exception e){ Log.i("demo" , "run stop error:" + Log.getStackTraceString(e)); } } }
这里主要就是:
加载插件Activity
使用反射将代理对象设置给插件Activity
测试插件Activity中的生命周期方法
运行程序,将上面的PluginActivity.apk放到宿主项目的cache目录下:
1 $ adb push PluginActivity.apk /data/data/com.example.dynamic.activity/cache
运行宿主项目就能看到效果了。同时我们打印一下Log:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ adb logcat -s demo ... I/demo (27983): proxy onStart I/demo (27983): onStart I/demo (27983): proxy onResume I/demo (27983): onResume I/demo (27983): proxy onPause I/demo (27983): onPause I/demo (27983): proxy onStop I/demo (27983): onStop I/demo (27983): proxy onDestroy I/demo (27983): onDestory I/demo (28565): proxy onStart I/demo (28565): onStart I/demo (28565): proxy onResume I/demo (28565): onResume I/demo (28565): proxy onPause I/demo (28565): onPause I/demo (28565): proxy onStop I/demo (28565): onStop I/demo (28565): proxy onDestroy I/demo (28565): onDestory
插件Activity的每个生命周期的方法也是都执行了。项目下载
两种方式的比较 第一种方式:使用反射机制来实现 优点:可以不用太多的关心插件中的Activity的生命周期方法,因为他加载进来之后就是一个真正意义上的Activity了 缺点:需要在宿主工程中进行声明,如果插件中的Activity多的话,那么就不灵活了。 第二种方式:使用代理机制来实现 优点:不需要在宿主工程中进行声明太多的Activity了,只需要有一个代理Activity的声明就可以了,很灵活 缺点:需要管理手动的去管理插件中Activity的生命周期方法,难度复杂。reference http://blog.csdn.net/jiangwei0910410003/article/details/48104455