1. 程式人生 > >讓我們來聊一聊外掛化吧---高深莫測

讓我們來聊一聊外掛化吧---高深莫測

現如今外掛化的思想和應用在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