让我们来聊一聊插件化吧---高深莫测

现如今插件化的思想和应用在Android上越来越多了,各式各样的方案也是层出不穷,这篇文章旨在告诉大家插件化的核心思想是什么,又有什么样的实现方式。

前言


首先,这篇文章的题目为什么不沿用我之前xxxx!xxxxx这样的风格呢,因为我觉得这样风格太中二了。。

其次,我写这篇文章的原因是因为前些时候看到有大神写了一篇文章Android 插件化的 过去 现在 未来,里面的内容很不错,特别是有一些关于原理的东西,让我回想起当时看几个插件化框架的源码的时候产生的心得和体会,这里也是写出来给大家做一个分享吧。

插件化介绍


在开始真正讲解插件化之前,让我先告诉那些不了解插件化是什么的同学[什么是插件化]。

所谓插件化,就是让我们的应用不必再像原来一样把所有的内容都放在一个apk中,可以把一些功能和逻辑单独抽出来放在插件apk中,然后主apk做到[按需调用],这样的好处是一来可以减少主apk的体积,让应用更轻便,二来可以做到热插拔,更加动态化。

在后文中,我首先会对插件化的实现原理进行一个分析,接着我挑了其中两个比较有代表性的,[Small]和[DroidPlugin]来分析他们的实现有什么区别。

原理分析


在分析原理之前,让我们先想想做插件化会遇到的挑战。大家可以想一想,如果我要做一个插件apk,里面会包含什么?首先想到的肯定是activity和对应的资源,我们要做的事就是在主apk中调用插件apk中的activity,并且加载对应的资源。当然这只是其中的一个挑战,这里我不会带大家分析所有的原理,因为这样一天一夜都讲不完,所以我选取了其中最具代表性的一点:[主apk如何加载插件apk中的activity]。

首先,我们回想一下平时我们是怎么唤起一个activity的:

1
2
Intent intent = new Intent(ActivityA.this,ActivityB.class);
startActivity(intent);

我们调用的是activity的startActivity方法,而我们都知道我们的activity是继承自ContextThemeWrapper的,而ContextThemeWrapper只是一个包装类,真正的逻辑在ContextImpl中,让我们看看其中做了什么。


1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void startActivity(Intent intent, Bundle options) {
    warnIfCallingFromSystemProcess();
    if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
        throw new AndroidRuntimeException(
                "Calling startActivity() from outside of an Activity "
                + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                + " Is this really what you want?");
    }
    mMainThread.getInstrumentation().execStartActivity(
        getOuterContext(), mMainThread.getApplicationThread(), null,
        (Activity)null, intent, -1, options);
}

其中调用了mMainThread.getInstrumentation()获取一个Instrumentation对象,这个对象大家可以看作是activity的管家,对activity的操作都会调用它去执行。让我们看看它的execStartActivity方法。


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
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    IApplicationThread whoThread = (IApplicationThread) contextThread;
    if (mActivityMonitors != null) {
        synchronized (mSync) {
            final int N = mActivityMonitors.size();
            for (int i=0; i<N; i++) {
                final ActivityMonitor am = mActivityMonitors.get(i);
                if (am.match(who, null, intent)) {
                    am.mHits++;
                    if (am.isBlocking()) {
                        return requestCode >= 0 ? am.getResult() : null;
                    }
                    break;
                }
            }
        }
    }
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess();
        int result = ActivityManagerNative.getDefault()
            .startActivity(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, target != null ? target.mEmbeddedID : null,
                    requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
    }
    return null;
}
 

这个方法最关键的一点就是调用了ActivityManagerNative.getDefault()去获取一个ActivityManagerNative对象并且调用了它的startActivity方法。

1
public abstract class ActivityManagerNative extends Binder implements IActivityManager


从它的定义就可以看出它是和aidl相关的,对于aidl这里我不细讲,大家可以自行查阅相关资料,概括来说就是Android中一种跨进程的通信方式。

那既然是跨进程的,通过ActivityManagerNative跨到了哪个进程呢?答案是ActivityManagerService,简称AMS,它是ActivityManagerNative的实现类。是Android framework层最最最重要的几个类之一,是运行在Android内核进程的。下面让我们看看AMS的startActivity方法。


1
 2
3
4
5
6
7
8
9
@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
        Intent intent, String resolvedType, IBinder resultTo,
        String resultWho, int requestCode, int startFlags,
        String profileFile, ParcelFileDescriptor profileFd, Bundle options) {
    return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
            resultWho, requestCode,
            startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId());
}
 

可以看到调用了startActivityAsUser。而在startActivityAsUser方法中调用了ActivityStackSupervisor的startActivityMayWait方法。


1
2
3
4
5
6
7
8
9
10
final int startActivityMayWait(IApplicationThread caller, int callingUid,String callingPackage, Intent intent, String resolvedType, IBinder resultTo,String resultWho, int requestCode, int startFlags, String profileFile,
ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,Bundle options, int userId) {

     ..........

     int res = startActivityLocked(caller, intent, resolvedType,aInfo, resultTo, resultWho, requestCode, callingPid, callingUid,callingPackage, startFlags, options, componentSpecified, null);

    .........
  }
}
 

这个方法内容很多,前面主要是对一些权限的判断,这个我们等等再讲,而在判断完权限之后,调用了startActivityLocked方法。

在调用了startActivityLocked方法之后,是一系列和ActivityStack这个类的交互,这其中的过程我这里不分析了,从ActivityStack这个类的名字就可以看出它是和Activity栈相关的,交互的主要目的也就是处理activity栈的需求。最后会调用到realStartActivityLocked方法。


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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
    final boolean realStartActivityLocked(ActivityRecord r,ProcessRecord app, boolean andResume, boolean checkConfig)
        throws RemoteException {

    r.startFreezingScreenLocked(app, 0);
    if (false) Slog.d(TAG, "realStartActivity: setting app visibility true");
    mWindowManager.setAppVisibility(r.appToken, true);

    // schedule launch ticks to collect information about slow apps.
    r.startLaunchTickingLocked();

    // Have the window manager re-evaluate the orientation of
    // the screen based on the new activity order.  Note that
    // as a result of this, it can call back into the activity
    // manager with a new orientation.  We don't care about that,
    // because the activity is not currently running so we are
    // just restarting it anyway.
    if (checkConfig) {
        Configuration config = mWindowManager.updateOrientationFromAppTokens(
                mService.mConfiguration,
                r.mayFreezeScreenLocked(app) ? r.appToken : null);
        mService.updateConfigurationLocked(config, r, false, false);
    }

    r.app = app;
    app.waitingToKill = null;
    r.launchCount++;
    r.lastLaunchTime = SystemClock.uptimeMillis();

    if (localLOGV) Slog.v(TAG, "Launching: " + r);

    int idx = app.activities.indexOf(r);
    if (idx < 0) {
        app.activities.add(r);
    }
    mService.updateLruProcessLocked(app, true, true);

    final ActivityStack stack = r.task.stack;
    try {
        if (app.thread == null) {
            throw new RemoteException();
        }
        List<ResultInfo> results = null;
        List<Intent> newIntents = null;
        if (andResume) {
            results = r.results;
            newIntents = r.newIntents;
        }
        if (DEBUG_SWITCH) Slog.v(TAG, "Launching: " + r
                + " icicle=" + r.icicle
                + " with results=" + results + " newIntents=" + newIntents
                + " andResume=" + andResume);
        if (andResume) {
            EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY,
                    r.userId, System.identityHashCode(r),
                    r.task.taskId, r.shortComponentName);
        }
        if (r.isHomeActivity() && r.isNotResolverActivity()) {
            // Home process is the root process of the task.
            mService.mHomeProcess = r.task.mActivities.get(0).app;
        }
        mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName());
        r.sleeping = false;
        r.forceNewConfig = false;
        mService.showAskCompatModeDialogLocked(r);
        r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo);
        String profileFile = null;
        ParcelFileDescriptor profileFd = null;
        boolean profileAutoStop = false;
        if (mService.mProfileApp != null && mService.mProfileApp.equals(app.processName)) {
            if (mService.mProfileProc == null || mService.mProfileProc == app) {
                mService.mProfileProc = app;
                profileFile = mService.mProfileFile;
                profileFd = mService.mProfileFd;
                profileAutoStop = mService.mAutoStopProfiler;
            }
        }
        app.hasShownUi = true;
        app.pendingUiClean = true;
        if (profileFd != null) {
            try {
                profileFd = profileFd.dup();
            } catch (IOException e) {
                if (profileFd != null) {
                    try {
                        profileFd.close();
                    } catch (IOException o) {
                    }
                    profileFd = null;
                }
            }
        }
        app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
        app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                System.identityHashCode(r), r.info,
                new Configuration(mService.mConfiguration), r.compat,
                app.repProcState, r.icicle, results, newIntents, !andResume,
                mService.isNextTransitionForward(), profileFile, profileFd,
                profileAutoStop);

        if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
            // This may be a heavy-weight process!  Note that the package
            // manager will ensure that only activity can run in the main
            // process of the .apk, which is the only thing that will be
            // considered heavy-weight.
            if (app.processName.equals(app.info.packageName)) {
                if (mService.mHeavyWeightProcess != null
                        && mService.mHeavyWeightProcess != app) {
                    Slog.w(TAG, "Starting new heavy weight process " + app
                            + " when already running "
                            + mService.mHeavyWeightProcess);
                }
                mService.mHeavyWeightProcess = app;
                Message msg = mService.mHandler.obtainMessage(
                        ActivityManagerService.POST_HEAVY_NOTIFICATION_MSG);
                msg.obj = r;
                mService.mHandler.sendMessage(msg);
            }
        }

    } catch (RemoteException e) {
        if (r.launchFailed) {
            // This is the second time we failed -- finish activity
            // and give up.
            Slog.e(TAG, "Second failure launching "
                  + r.intent.getComponent().flattenToShortString()
                  + ", giving up", e);
            mService.appDiedLocked(app, app.pid, app.thread);
            stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
                    "2nd-crash", false);
            return false;
        }

        // This is the first time we failed -- restart process and
        // retry.
        app.activities.remove(r);
        throw e;
    }

    r.launchFailed = false;
    if (stack.updateLRUListLocked(r)) {
        Slog.w(TAG, "Activity " + r
              + " being launched, but already in LRU list");
    }

    if (andResume) {
        // As part of the process of launching, ActivityThread also performs
        // a resume.
        stack.minimalResumeActivityLocked(r);
    } else {
        // This activity is not starting in the resumed state... which
        // should look like we asked it to pause+stop (but remain visible),
        // and it has done so and reported back the current icicle and
        // other state.
        if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r
                + " (starting in stopped state)");
        r.state = ActivityState.STOPPED;
        r.stopped = true;
    }

    // Launch the new version setup screen if needed.  We do this -after-
    // launching the initial activity (that is, home), so that it can have
    // a chance to initialize itself while in the background, making the
    // switch back to it faster and look better.
    if (isFrontStack(stack)) {
        mService.startSetupActivityLocked();
    }

    return true;
}
 

这么长的方法,看的头都晕了,不过没关系,我们看重点的。


1
2
3
4
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,System.identityHashCode(r), r.info,new Configuration(mService.mConfiguration), r.compat,
app.repProcState, r.icicle, results, newIntents, !andResume,
mService.isNextTransitionForward(), profileFile, profileFd,
profileAutoStop);
 

重点来了,调用了ApplicationThread的scheduleLaunchActivity方法。大家千万不要被这个类的名字所迷惑了,以为他是一个线程。其实它不仅是线程,还是一个Binder对象,也是和aidl相关的,实现了IApplicationThread接口,定义在ActivityThread类的内部。那我们为什么要用它呢?回想一下,刚刚在Instrumentation里调用了AMS的startActivity之后的所有操作,都是在系统进程中进行的,而现在我们要返回到我们自己的app进程,同样是跨进程,我们需要一个aidl框架去完成,所以这里才会有ApplicationThread。让我们看看具体的方法吧。


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
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,int procState, Bundle state, List<ResultInfo> pendingResults,List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) {

        updateProcessState(procState, false);

        ActivityClientRecord r = new ActivityClientRecord();

        r.token = token;
        r.ident = ident;
        r.intent = intent;
        r.activityInfo = info;
        r.compatInfo = compatInfo;
        r.state = state;

        r.pendingResults = pendingResults;
        r.pendingIntents = pendingNewIntents;

        r.startsNotResumed = notResumed;
        r.isForward = isForward;

        r.profileFile = profileName;
        r.profileFd = profileFd;
        r.autoStopProfiler = autoStopProfiler;

        updatePendingConfiguration(curConfig);

        queueOrSendMessage(H.LAUNCH_ACTIVITY, r);
    }
 

在最后调用了queueOrSendMessage(H.LAUNCH_ACTIVITY, r)这个方法。

而这里的H其实是一个handler,queueOrSendMessage方法的作用就是通过handler去send一个message。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void handleMessage(Message msg) {    
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                ActivityClientRecord r = (ActivityClientRecord)msg.obj;

                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo, r.compatInfo);
                handleLaunchActivity(r, null);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;

     ..........
}
 

我们看H的handleMessage,如果message是我们刚刚发送的LAUNCH_ACTIVITY,则调用handleLaunchActivity方法。而在这个方法中,调用了performLaunchActivity去创建一个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
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
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");

    ActivityInfo aInfo = r.activityInfo;
    if (r.packageInfo == null) {
        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                Context.CONTEXT_INCLUDE_CODE);
    }

    ComponentName component = r.intent.getComponent();
    if (component == null) {
        component = r.intent.resolveActivity(
            mInitialApplication.getPackageManager());
        r.intent.setComponent(component);
    }

    if (r.activityInfo.targetActivity != null) {
        component = new ComponentName(r.activityInfo.packageName,
                r.activityInfo.targetActivity);
    }

    Activity activity = null;
    try {
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                "Unable to instantiate activity " + component
                + ": " + e.toString(), e);
        }
    }

    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
        if (localLOGV) Slog.v(
                TAG, r + ": app=" + app
                + ", appName=" + app.getPackageName()
                + ", pkg=" + r.packageInfo.getPackageName()
                + ", comp=" + r.intent.getComponent().toShortString()
                + ", dir=" + r.packageInfo.getAppDir());

        if (activity != null) {
            Context appContext = createBaseContextForActivity(r, activity);
            CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
            Configuration config = new Configuration(mCompatConfiguration);
            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                    + r.activityInfo.name + " with config " + config);
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config);

            if (customIntent != null) {
                activity.mIntent = customIntent;
            }
            r.lastNonConfigurationInstances = null;
            activity.mStartedActivity = false;
            int theme = r.activityInfo.getThemeResource();
            if (theme != 0) {
                activity.setTheme(theme);
            }

            activity.mCalled = false;
            mInstrumentation.callActivityOnCreate(activity, r.state);
            if (!activity.mCalled) {
                throw new SuperNotCalledException(
                    "Activity " + r.intent.getComponent().toShortString() +
                    " did not call through to super.onCreate()");
            }
            r.activity = activity;
            r.stopped = true;
            if (!r.activity.mFinished) {
                activity.performStart();
                r.stopped = false;
            }
            if (!r.activity.mFinished) {
                if (r.state != null) {
                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                }
            }
            if (!r.activity.mFinished) {
                activity.mCalled = false;
                mInstrumentation.callActivityOnPostCreate(activity, r.state);
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onPostCreate()");
                }
            }
        }
        r.paused = true;

        mActivities.put(r.token, r);

    } catch (SuperNotCalledException e) {
        throw e;

    } catch (Exception e) {
        if (!mInstrumentation.onException(activity, e)) {
            throw new RuntimeException(
                "Unable to start activity " + component
                + ": " + e.toString(), e);
        }
    }

    return activity;
}
 

方法比较长,其中核心的内容是:


1
2
3
4
5
6
7
8
9
10
try {
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
}
 

又调用了Instrumentation的newActivity去创建一个Activity。

至此,启动一个activity的过程就分析完了,让我们来总结一下。

(1) 我们app中是使用了Activity的startActivity方法,具体调用的是ContextImpl的同名函数。

(2) ContextImpl中会调用Instrumentation的execStartActivity方法。

(3) Instrumentation通过aidl进行跨进程通信,最终调用AMS的startActivity方法。

(4) 在系统进程中AMS中先判断权限,然后通过调用ActivityStackSupervisor和ActivityStack进行一系列的交互用来确定Activity栈的使用方式。

(5) 通过ApplicationThread进行跨进程通信,转回到app进程。

(6) 通过ActivityThread中的H(一个handler)传递消息,最终调用Instrumentation来创建一个Activity。

其实如果大家看过其他有关Android系统的调用,比如启动一个Service之类的,整个过程都是大同小异的,无非就是跨进程和AMS,WMS或者PMS进行通信,然后通过ApplicationThread回到app进程最后通过handler传递消息。

好了,说了这么多,这和我们的插件化有什么关系呢?大家想一想,如果我们要在主apk中启动一个插件apk的Activity,上面的哪一步会出问题?大家好好想一想,想一想,一想,想。。。。

没错!就是(4),第四步,权限验证会通不过,为啥呢?因为我们主apk的manifest中没有定义插件apk的Activity啊!

让我们回到代码,看看ActivityStackSupervisor的startActivityMayWait方法,关于权限验证的内容都在里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (err == ActivityManager.START_SUCCESS && aInfo == null) {   
		// We couldn't find the specific class specified in the Intent.
        // Also the end of the line.
        err = ActivityManager.START_CLASS_NOT_FOUND;
}

if (err != ActivityManager.START_SUCCESS) {
            if (resultRecord != null) {
                resultStack.sendActivityResultLocked(-1,
                    resultRecord, resultWho, requestCode,
                    Activity.RESULT_CANCELED, null);
            }
            setDismissKeyguard(false);
            ActivityOptions.abort(options);
            return err;
}

在这个方法中有这么一段,而在Instrumentation中会去检查这个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void checkStartActivityResult(int res, Object intent) {
	if (res >= ActivityManager.START_SUCCESS) {
        return;
    }

    switch (res) {
        case ActivityManager.START_INTENT_NOT_RESOLVED:
        case ActivityManager.START_CLASS_NOT_FOUND:
            if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                throw new ActivityNotFoundException(
                        "Unable to find explicit activity class "
                        + ((Intent)intent).getComponent().toShortString()
                        + "; have you declared this activity in your AndroidManifest.xml?");
            throw new ActivityNotFoundException(
                    "No Activity found to handle " + intent);
        .....
}

如果找不到对应的Activity,直接抛出错误。

唔。。怎么办呢?找不到Activity,也许你会觉得直接在主apk的manifest中实现定义就好了,但是这是不可能的,因为你怎么知道插件apk中有什么Activity呢?如果以后要动态的修改插件apk中的Activity,难道你的主apk也要对应的一次次修改吗?

不要慌,办法都是人想出来的,让我们看看Samll和DroidPlugin的解决办法吧。

思考解决方案

首先我们要明确,要解决的核心问题是[如何能让没有在manifst中注册的Activity能启动起来]。由于权限验证机制是系统做的,我们肯定是没办法修改的,既然我们没办法修改,那是不是考虑去欺骗呢?也就是说可以在manifest中预先定义好几个Activity,俗称占坑,比如名字就叫ActivityA,ActivityB,在校验权限之前把我们插件apk中的Activity替换成定义好的Activity,这样就能顺利通过校验,而在之后真正生成Activity的地方再换回来,瞒天过海。

那怎么去欺骗呢?回归前面的代码,其实答案已经呼之欲出了——我们可以有两种选择,hook Instrumentation或者hook ActivityManagerNative。这也正好对应了Small和DroidPlugin的实现方案。

Small实现方式

首先,让我们看一下Small的实现方式,大家可以去它的GitHub上下载源码。

上文提到,Samll的实现方式是hook Instrumentation,让我们从代码上来看,我们看它的ApkBundleLauncher类,它内部有一个setUp方法。

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
@Override
public void setUp(Context context) {
    super.setUp(context);
    // Inject instrumentation
    if (sHostInstrumentation == null) {
        try {
            final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            final Method method = activityThreadClass.getMethod("currentActivityThread");
            Object thread = method.invoke(null, (Object[]) null);
            Field field = activityThreadClass.getDeclaredField("mInstrumentation");
            field.setAccessible(true);
            sHostInstrumentation = (Instrumentation) field.get(thread);
            Instrumentation wrapper = new InstrumentationWrapper();
            field.set(thread, wrapper);

            if (context instanceof Activity) {
                field = Activity.class.getDeclaredField("mInstrumentation");
                field.setAccessible(true);
                field.set(context, wrapper);
            }
        } catch (Exception ignored) {
            ignored.printStackTrace();
            // Usually, cannot reach here
        }
    }
}
 

可以看到它通过反射获取了Instrumentation并且赋值成了自定义的InstrumentationWrapper。

而在自己定义的InstrumentationWrapper中,会去重写两个重要的方法,那就是execStartActivity和newActivity这两个方法。为什么会选择这两个方法呢?因为我们前面说过,在第一个方法中,系统会去校验权限,而第二个方法则是真正生成Activity实例的方法,我们通过重写两个方法,可以做到我们之前提到的[在execStartActivity的时候把Activity替换成自己的],而[在newActivity又换回成真正的Activity],从而做到[欺骗]的效果。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** @Override V21+
 * Wrap activity from REAL to STUB */
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, android.os.Bundle options) {
    wrapIntent(intent);
    return ReflectAccelerator.execStartActivityV21(sHostInstrumentation,
            who, contextThread, token, target, intent, requestCode, options);
}

/** @Override V20-
 * Wrap activity from REAL to STUB */
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode) {
    wrapIntent(intent);
    return ReflectAccelerator.execStartActivityV20(sHostInstrumentation,
            who, contextThread, token, target, intent, requestCode);
}
 

可以看到Wrapper首先根据sdk版本重写了两个不同的方法,这里我们只关注一个就可以,先看wrapIntent方法。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void wrapIntent(Intent intent) {
    ComponentName component = intent.getComponent();
    if (component == null) return; // ignore system intent

    String realClazz = intent.getComponent().getClassName();
    if (sLoadedActivities == null) return;

    ActivityInfo ai = sLoadedActivities.get(realClazz);
    if (ai == null) return;

    // Carry the real(plugin) class for incoming `newActivity' method.
    intent.addCategory(REDIRECT_FLAG + realClazz);
    String stubClazz = dequeueStubActivity(ai, realClazz);
    intent.setComponent(new ComponentName(Small.getContext(), stubClazz));
}
 

这个方法的神奇之处在于它先获取了真正的Activity的信息并且保存起来,然后调用了dequeueStubActivity方法去生成一个[占坑]的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
26
27
28
29
30
31
32
33
private String dequeueStubActivity(ActivityInfo ai, String realActivityClazz) {
    if (ai.launchMode == ActivityInfo.LAUNCH_MULTIPLE) {
        // In standard mode, the stub activity is reusable.
        return STUB_ACTIVITY_PREFIX;
    }

    int availableId = -1;
    int stubId = -1;
    int countForMode = STUB_ACTIVITIES_COUNT;
    int countForAll = countForMode * 3; // 3=[singleTop, singleTask, singleInstance]
    if (mStubQueue == null) {
        // Lazy init
        mStubQueue = new String[countForAll];
    }
    int offset = (ai.launchMode - 1) * countForMode;
    for (int i = 0; i < countForMode; i++) {
        String usedActivityClazz = mStubQueue[i + offset];
        if (usedActivityClazz == null) {
            if (availableId == -1) availableId = i;
        } else if (usedActivityClazz.equals(realActivityClazz)) {
            stubId = i;
        }
    }
    if (stubId != -1) {
        availableId = stubId;
    } else if (availableId != -1) {
        mStubQueue[availableId + offset] = realActivityClazz;
    } else {
        // TODO:
        Log.e(TAG, "Launch mode " + ai.launchMode + " is full");
    }
    return STUB_ACTIVITY_PREFIX + ai.launchMode + availableId;
}
 

可以看到其中根据真正的Activity的launchMode等因素生成一个占坑Activity,而这些占坑Activity都是定义在manifest中的。


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
<application>
    <!-- Stub Activities -->
    <!-- 1 standard mode -->
    <activity android:name=".A" android:launchMode="standard"/>
    <!-- 4 singleTask mode -->
    <activity android:name=".A10" android:launchMode="singleTask"/>
    <activity android:name=".A11" android:launchMode="singleTask"/>
    <activity android:name=".A12" android:launchMode="singleTask"/>
    <activity android:name=".A13" android:launchMode="singleTask"/>
    <!-- 4 singleTop mode -->
    <activity android:name=".A20" android:launchMode="singleTop"/>
    <activity android:name=".A21" android:launchMode="singleTop"/>
    <activity android:name=".A22" android:launchMode="singleTop"/>
    <activity android:name=".A23" android:launchMode="singleTop"/>
    <!-- 4 singleInstance mode -->
    <activity android:name=".A30" android:launchMode="singleInstance"/>
    <activity android:name=".A31" android:launchMode="singleInstance"/>
    <activity android:name=".A32" android:launchMode="singleInstance"/>
    <activity android:name=".A33" android:launchMode="singleInstance"/>

    <!-- Web Activity -->
    <activity android:name=".webkit.WebActivity"
        android:screenOrientation="portrait"
        android:windowSoftInputMode="stateHidden|adjustPan"/>
    <!--<service android:name="net.wequick.small.service.UpgradeService"-->
        <!--android:exported="false"/>-->
</application>
 

通过这样的方式我们就顺利的完成了[瞒天过海]的第一步,在之后AMS的权限校验中就能顺利通过了。接着让我们来看newActivity方法。


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
@Override
/** Unwrap activity from STUB to REAL */
public Activity newActivity(ClassLoader cl, String className, Intent intent)
        throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    // Stub -> Real
    if (!className.startsWith(STUB_ACTIVITY_PREFIX)) {
        return super.newActivity(cl, className, intent);
    }
    className = unwrapIntent(intent, className);
    Activity activity = super.newActivity(cl, className, intent);
    return activity;
}

private String unwrapIntent(Intent intent, String className) {
            Set<String> categories = intent.getCategories();
            if (categories == null) return className;

            // Get plugin activity class name from categories
            Iterator<String> it = categories.iterator();
            String realClazz = null;
            while (it.hasNext()) {
                String category = it.next();
                if (category.charAt(0) == REDIRECT_FLAG) {
                    realClazz = category.substring(1);
                    break;
                }
            }
            if (realClazz == null) return className;
            return realClazz;
}
 

这里就更简单了,通过unwrapIntent获取真正的Activity并且调用父类的newActivity方法,也就是Instrumentation去生成一个Activity。

到这儿,Small的解决方案就基本讲完了,原理是很简单的,就是通过反射替换掉Instrumentation,然后在里面做文章。


DroidPlugin实现方式


DroidPlugin是另外一种插件化框架,它采取的方案是hook整个ActivityManagerNative。

首先让我们看它其中的IActivityManagerHook类。


1
public class IActivityManagerHook extends ProxyHook

它继承自ProxyHook。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class ProxyHook extends Hook implements InvocationHandler {

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        try {
            if (!isEnable()) {
                return method.invoke(mOldObj, args);
            }
            HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method);
            if (hookedMethodHandler != null) {
                return hookedMethodHandler.doHookInner(mOldObj, method, args);
            }
            return method.invoke(mOldObj, args);
        }

        ...........
}
 

ProxyHook实现了InvocationHandler接口,也就是说它是用于动态代理的,在invoke方法中先通过mHookHandles去获取对应的hookedMethodHandler,这里的mHookHandles在我们对应的情况下是IActivityManagerHookHandle。


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
public class IActivityManagerHookHandle extends BaseHookHandle {
	
    @Override
    protected void init() {
        sHookedMethodHandlers.put("startActivity", new startActivity(mHostContext));
        sHookedMethodHandlers.put("startActivityAsUser", new startActivityAsUser(mHostContext));
        sHookedMethodHandlers.put("startActivityAsCaller", new startActivityAsCaller(mHostContext));
        sHookedMethodHandlers.put("startActivityAndWait", new startActivityAndWait(mHostContext));
        sHookedMethodHandlers.put("startActivityWithConfig", new startActivityWithConfig(mHostContext));
        sHookedMethodHandlers.put("startActivityIntentSender", new startActivityIntentSender(mHostContext));
        sHookedMethodHandlers.put("startVoiceActivity", new startVoiceActivity(mHostContext));
        sHookedMethodHandlers.put("startNextMatchingActivity", new startNextMatchingActivity(mHostContext));
        sHookedMethodHandlers.put("startActivityFromRecents", new startActivityFromRecents(mHostContext));
        sHookedMethodHandlers.put("finishActivity", new finishActivity(mHostContext));
        sHookedMethodHandlers.put("registerReceiver", new registerReceiver(mHostContext));
        sHookedMethodHandlers.put("broadcastIntent", new broadcastIntent(mHostContext));
        sHookedMethodHandlers.put("unbroadcastIntent", new unbroadcastIntent(mHostContext));
        sHookedMethodHandlers.put("getCallingPackage", new getCallingPackage(mHostContext));
        sHookedMethodHandlers.put("getCallingActivity", new getCallingActivity(mHostContext));
        sHookedMethodHandlers.put("getAppTasks", new getAppTasks(mHostContext));
        sHookedMethodHandlers.put("addAppTask", new addAppTask(mHostContext));
        sHookedMethodHandlers.put("getTasks", new getTasks(mHostContext));
        sHookedMethodHandlers.put("getServices", new getServices(mHostContext));
        sHookedMethodHandlers.put("getProcessesInErrorState", new getProcessesInErrorState(mHostContext));
        sHookedMethodHandlers.put("getContentProvider", new getContentProvider(mHostContext));
        sHookedMethodHandlers.put("getContentProviderExternal", new getContentProviderExternal(mHostContext));
        sHookedMethodHandlers.put("removeContentProviderExternal", new removeContentProviderExternal(mHostContext));
        sHookedMethodHandlers.put("publishContentProviders", new publishContentProviders(mHostContext));
        sHookedMethodHandlers.put("getRunningServiceControlPanel", new getRunningServiceControlPanel(mHostContext));
        sHookedMethodHandlers.put("startService", new startService(mHostContext));
        sHookedMethodHandlers.put("stopService", new stopService(mHostContext));
        sHookedMethodHandlers.put("stopServiceToken", new stopServiceToken(mHostContext));
        sHookedMethodHandlers.put("setServiceForeground", new setServiceForeground(mHostContext));
        sHookedMethodHandlers.put("bindService", new bindService(mHostContext));
        sHookedMethodHandlers.put("publishService", new publishService(mHostContext));
        sHookedMethodHandlers.put("unbindFinished", new unbindFinished(mHostContext));
        sHookedMethodHandlers.put("peekService", new peekService(mHostContext));
        sHookedMethodHandlers.put("bindBackupAgent", new bindBackupAgent(mHostContext));
        sHookedMethodHandlers.put("backupAgentCreated", new backupAgentCreated(mHostContext));
        sHookedMethodHandlers.put("unbindBackupAgent", new unbindBackupAgent(mHostContext));
        sHookedMethodHandlers.put("killApplicationProcess", new killApplicationProcess(mHostContext));
        sHookedMethodHandlers.put("startInstrumentation", new startInstrumentation(mHostContext));
        sHookedMethodHandlers.put("getActivityClassForToken", new getActivityClassForToken(mHostContext));
        sHookedMethodHandlers.put("getPackageForToken", new getPackageForToken(mHostContext));
        sHookedMethodHandlers.put("getIntentSender", new getIntentSender(mHostContext));
        sHookedMethodHandlers.put("clearApplicationUserData", new clearApplicationUserData(mHostContext));
        sHookedMethodHandlers.put("handleIncomingUser", new handleIncomingUser(mHostContext));
        sHookedMethodHandlers.put("grantUriPermission", new grantUriPermission(mHostContext));
        sHookedMethodHandlers.put("getPersistedUriPermissions", new getPersistedUriPermissions(mHostContext));
        sHookedMethodHandlers.put("killBackgroundProcesses", new killBackgroundProcesses(mHostContext));
        sHookedMethodHandlers.put("forceStopPackage", new forceStopPackage(mHostContext));
        sHookedMethodHandlers.put("getRunningAppProcesses", new getRunningAppProcesses(mHostContext));
        sHookedMethodHandlers.put("getRunningExternalApplications", new getRunningExternalApplications(mHostContext));
        sHookedMethodHandlers.put("getMyMemoryState", new getMyMemoryState(mHostContext));
        sHookedMethodHandlers.put("crashApplication", new crashApplication(mHostContext));
        sHookedMethodHandlers.put("grantUriPermissionFromOwner", new grantUriPermissionFromOwner(mHostContext));
        sHookedMethodHandlers.put("checkGrantUriPermission", new checkGrantUriPermission(mHostContext));
        sHookedMethodHandlers.put("startActivities", new startActivities(mHostContext));
        sHookedMethodHandlers.put("getPackageScreenCompatMode", new getPackageScreenCompatMode(mHostContext));
        sHookedMethodHandlers.put("setPackageScreenCompatMode", new setPackageScreenCompatMode(mHostContext));
        sHookedMethodHandlers.put("getPackageAskScreenCompat", new getPackageAskScreenCompat(mHostContext));
        sHookedMethodHandlers.put("setPackageAskScreenCompat", new setPackageAskScreenCompat(mHostContext));
        sHookedMethodHandlers.put("navigateUpTo", new navigateUpTo(mHostContext));
        sHookedMethodHandlers.put("serviceDoneExecuting", new serviceDoneExecuting(mHostContext));


    }
}
 

可以看到在init中生成了很多类,而我们在invoke方法中就会根据对应的方法名拿到对应的类。

回想一下,我们在这里对应的是什么方法?根据前面的文章,我们知道是startActivity方法,进而拿到的是startActivity类。

回到invoke方法,拿到HookedMethodHandler后,会执行它的doInnerHook方法。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public synchronized Object doHookInner(Object receiver, Method method, Object[] args) throws Throwable {
    long b = System.currentTimeMillis();
    try {
        mUseFakedResult = false;
        mFakedResult = null;
        boolean suc = beforeInvoke(receiver, method, args);
        Object invokeResult = null;
        if (!suc) {
            invokeResult = method.invoke(receiver, args);
        }
        afterInvoke(receiver, method, args, invokeResult);
        if (mUseFakedResult) {
            return mFakedResult;
        } else {
            return invokeResult;
        }
    } finally {
        long time = System.currentTimeMillis() - b;
        if (time > 5) {
            Log.i(TAG, "doHookInner method(%s.%s) cost %s ms", method.getDeclaringClass().getName(), method.getName(), time);
        }
    }
}
 

可以看到这个方法也是AOP的,在真正的调用method.invoke之前和之后会对应的调用beforeInvoke和afterInvoke。这两个方法是有待HookedMethodHandler的子类去实现的,这里是startActivity这个子类。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {

    RunningActivities.beforeStartActivity();
    boolean bRet;
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
        bRet = doReplaceIntentForStartActivityAPILow(args);
    } else {
        bRet = doReplaceIntentForStartActivityAPIHigh(args);
    }
    if (!bRet) {
        setFakedResult(Activity.RESULT_CANCELED);
        return true;
    }

    return super.beforeInvoke(receiver, method, args);
}
 

根据sdk版本进入不同的方法,这里我们只看APIHigh。


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
protected boolean doReplaceIntentForStartActivityAPIHigh(Object[] args) throws RemoteException {
            int intentOfArgIndex = findFirstIntentIndexInArgs(args);
            if (args != null && args.length > 1 && intentOfArgIndex >= 0) {
                Intent intent = (Intent) args[intentOfArgIndex];
                //XXX String callingPackage = (String) args[1];
                if (!PluginPatchManager.getInstance().canStartPluginActivity(intent)) {
                    PluginPatchManager.getInstance().startPluginActivity(intent);
                    return false;
                }
                ActivityInfo activityInfo = resolveActivity(intent);
                if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) {
                    ComponentName component = selectProxyActivity(intent);
                    if (component != null) {
                        Intent newIntent = new Intent();
                        try {
                            ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(component.getPackageName());
                            setIntentClassLoader(newIntent, pluginClassLoader);
                        } catch (Exception e) {
                            Log.w(TAG, "Set Class Loader to new Intent fail", e);
                        }
                        newIntent.setComponent(component);
                        newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
                        newIntent.setFlags(intent.getFlags());


                        String callingPackage = (String) args[1];
                        if (TextUtils.equals(mHostContext.getPackageName(), callingPackage)) {
                        newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        args[intentOfArgIndex] = newIntent;
                        args[1] = mHostContext.getPackageName();
                    } else {
                        Log.w(TAG, "startActivity,replace selectProxyActivity fail");
                    }
                }
            }

            return true;
}
 

又是一个逻辑比较多的方法,但是很好理解,其实和Small做的事是差不多的,就是生成一个占坑的Intent,把真正的activity当作参数放在Intent中。

到这儿,就完成了第一步,我们要做的就是在对应的地方Hook掉这个AMS,至于怎么Hook大家自己去看源码,Hook的技巧不是重点,重点是Hook了什么。

那DroidPlugin是在哪里把Activity还原了呢?回想一下前文startActivity步骤6:

(6) 通过ActivityThread中的H(一个handler)传递消息,最终调用Instrumentation来创建一个Activity。

通过Handler去传递消息,让我们看看Handler的源码。


1
2
3
4
5
6
7
8
9
10
11
12
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
 

在dispatchMessage中,有一个CallBack,如果它不为空,就使用它去处理而不走Handler的handleMessage方法。DroidPlugin正是利用了这一点,自己去生成了一个CallBack并且通过反射注入到了ActivityThread的H类中。


1
2
3
4
5
6
7
8
9
@Override
public boolean handleMessage(Message msg) {

	 if (msg.what == LAUNCH_ACTIVITY) {
                return handleLaunchActivity(msg);
     }

     ...........
}
 

这是对应CallBack的handleMessage方法,如果message是LAUNCH_ACTIVITY,直接调用了handleLaunchActivity方法。


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
private boolean handleLaunchActivity(Message msg) {
    try {
        Object obj = msg.obj;
        Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent");
        //ActivityInfo activityInfo = (ActivityInfo) FieldUtils.readField(obj, "activityInfo", true);
        stubIntent.setExtrasClassLoader(mHostContext.getClassLoader());
        Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
        // 这里多加一个isNotShortcutProxyActivity的判断,因为ShortcutProxyActivity的很特殊,启动它的时候,
        // 也会带上一个EXTRA_TARGET_INTENT的数据,就会导致这里误以为是启动插件Activity,所以这里要先做一个判断。
        // 之前ShortcutProxyActivity错误复用了key,但是为了兼容,所以这里就先这么判断吧。
        if (targetIntent != null && !isShortcutProxyActivity(stubIntent)) {
            IPackageManagerHook.fixContextPackageManager(mHostContext);
            ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager());
            ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0);
            if (targetActivityInfo != null) {

                if (targetComponentName != null && targetComponentName.getClassName().startsWith(".")) {
                    targetIntent.setClassName(targetComponentName.getPackageName(), targetComponentName.getPackageName() + targetComponentName.getClassName());
                }

                ResolveInfo resolveInfo = mHostContext.getPackageManager().resolveActivity(stubIntent, 0);
                ActivityInfo stubActivityInfo = resolveInfo != null ? resolveInfo.activityInfo : null;
                if (stubActivityInfo != null) {
                    PluginManager.getInstance().reportMyProcessName(stubActivityInfo.processName, targetActivityInfo.processName, targetActivityInfo.packageName);
                }
                PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
                ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(targetComponentName.getPackageName());
                setIntentClassLoader(targetIntent, pluginClassLoader);
                setIntentClassLoader(stubIntent, pluginClassLoader);

                boolean success = false;
                try {
                    targetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
                    if (stubActivityInfo != null) {
                        targetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
                    }
                    success = true;
                } catch (Exception e) {
                    Log.e(TAG, "putExtra 1 fail", e);
                }

                if (!success && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
                    try {
                        ClassLoader oldParent = fixedClassLoader(pluginClassLoader);
                        targetIntent.putExtras(targetIntent.getExtras());

                        targetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
                        if (stubActivityInfo != null) {
                            targetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
                        }
                        fixedClassLoader(oldParent);
                        success = true;
                    } catch (Exception e) {
                        Log.e(TAG, "putExtra 2 fail", e);
                    }
                }

                if (!success) {
                    Intent newTargetIntent = new Intent();
                    newTargetIntent.setComponent(targetIntent.getComponent());
                    newTargetIntent.putExtra(Env.EXTRA_TARGET_INFO, targetActivityInfo);
                    if (stubActivityInfo != null) {
                        newTargetIntent.putExtra(Env.EXTRA_STUB_INFO, stubActivityInfo);
                    }
                    FieldUtils.writeDeclaredField(msg.obj, "intent", newTargetIntent);
                } else {
                    FieldUtils.writeDeclaredField(msg.obj, "intent", targetIntent);
                }
                FieldUtils.writeDeclaredField(msg.obj, "activityInfo", targetActivityInfo);

                Log.i(TAG, "handleLaunchActivity OK");
            } else {
                Log.e(TAG, "handleLaunchActivity oldInfo==null");
            }
        } else {
            Log.e(TAG, "handleLaunchActivity targetIntent==null");
        }
    } catch (Exception e) {
        Log.e(TAG, "handleLaunchActivity FAIL", e);
    }

    if (mCallback != null) {
        return mCallback.handleMessage(msg);
    } else {
        return false;
    }
}

 

代码依旧很长,但是核心就是拿到真正的Activity并且创建。

至此,DroidPlugin的逻辑也就理完了,和Small不同,它是hook了整个AMS并且通过反射替换了H类的CallBack,做到了[瞒天过海]。

两种方式的比较


那么这两种方式孰优孰劣呢?

我认为,DroidPlugin的方式是更加[屌]的,大家可以看上面IActivityManagerHookHandle这个类,在init方法中put的那些类,和Activity相关的只有一小部分,更多的是service,broadcast等等的操作。这是Small使用[Hook Instrumentation]所做不到的,因为Instrumentation只是Activity的管家,如果涉及到service这样的,它就无能为力了。

但是从另外一个方面说,Service的动态注册这样的需求其实是不多的,Hook Instrumentation基本已能满足大部分的场景的,另外Hook AMS需要的知识储备是要多得多的,拿我来说吧,看DroidPlugin的源码比看Small的源码困难太多了。。

这些都是要大家自己斟酌的。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值