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

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值