BruceFan's Blog

Stay hungry, stay foolish

0%

Android动态加载之免安装运行程序

本文说的关于动态加载的内容与之前一篇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
...
// set of instantiated backup agents, keyed by package name
final ArrayMap<String, BackupAgent> mBackupAgents = new ArrayMap<String, BackupAgent>();
/** Reference to singleton {@link ActivityThread} */
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;

// These can be accessed by multiple threads; mPackages is the lock.
// XXX For now we keep around information about all packages we have
// seen, not removing entries from this map.
// NOTE: The activity and window managers need to call in to
// ActivityThread to do things like update resource configurations,
// which means this lock gets held while the activity and window managers
// holds their own lock. Thus you MUST NEVER call back into the activity manager
// or window manager or anything that depends on them while holding this lock.
final ArrayMap<String, WeakReference<LoadedApk>> mPackages
= new ArrayMap<String, WeakReference<LoadedApk>>(); // 获取到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
/**
* Local state maintained about a currently loaded .apk.
* @hide
*/
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; // 一个Apk加载的类加载器
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 defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);

Environment.initForCurrentUser();

// Set the reporter for event logging in libcore
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[] {}); // 获取ActivityThread对象
String packageName = this.getPackageName();// 当前apk的包名
ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mPackages");
WeakReference wr = (WeakReference) mPackages.get(packageName); // 获取到LoadedApk对象
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
wr.get(), dLoader); // 设置LoadedApk中的mClassLoader的值为DexClassLoader
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

再点击按钮就能看到效果了。

  1. 因为要加载插件中的资源,所以需要调用loadResources方法
  2. 在测试过程中,发现插件项目中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
/**
* Base class for common functionality between various dex-based
* {@link ClassLoader} implementations.
*/
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;

/**
* Constructs an instance.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory directory where optimized dex files
* should be written; may be {@code null}
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
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
/**
* A pair of lists of entries, associated with a {@code ClassLoader}.
* One of the lists is a dex/resource path &mdash; typically referred
* to as a "class path" &mdash; list, and the other names directories
* containing native code libraries. Class path entries may be any of:
* a {@code .jar} or {@code .zip} file containing an optional
* top-level {@code classes.dex} file as well as arbitrary resources,
* or a plain {@code .dex} file (with no possibility of associated
* resources).
*
* <p>This class also contains methods to use these lists to look up
* classes and resources.</p>
*/
/*package*/ 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";

/** class definition context */
private final ClassLoader definingContext;

/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private final Element[] dexElements;

/** List of native library directories. */
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() {
//这里需要注意的是,不能调用super.onDestroy的方法了,不然报错,原因也很简单,这个Activity实质上不是真正的Activity了,没有生命周期的概念了,调用super的方法肯定报错
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();

//动态加载插件Activity
pluginClass = loader.loadClass("com.example.dynamicactivity.MainActivity");
Constructor<?> localConstructor = pluginClass.getConstructor(new Class[] {});
pluginActivity = localConstructor.newInstance(new Object[] {});

//将代理对象设置给插件Activity
Method setProxy = pluginClass.getMethod("setProxy",new Class[] { Activity.class });
setProxy.setAccessible(true);
setProxy.invoke(pluginActivity, new Object[] { this });

initMethodMap();

//调用它的onCreate方法
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