微信小程序任務棧實現原理

背景

以前面試一些校招同窗,聊到微信小程序是什麼launchMode,其任務棧是如何實現的?不少同窗只提到singleInstance,這可能沒那麼簡單。 今天咱們就猜想並解析一下微信主程序與小程序的關係與大體實現,最後給出源碼,能夠給你們做一個簡單參考。php

初探

既然要研究微信,那麼咱們就先打開幾個小程序,再用adb命令看看任務棧信息。 在終端使用 adb shell dumpsys activity activities 命令後,能夠找到最近任務列表的Activity信息:java

Running activities (most recent first):
  TaskRecord{caccd90 #3239 A=.AppBrandUI3 U=0 StackId=1 sz=1}
    Run #4: ActivityRecord{bb162b8 u0 com.tencent.mm/.plugin.appbrand.ui.AppBrandUI3 t3239}
  TaskRecord{d6c62d6 #3190 A=com.tencent.mm U=0 StackId=1 sz=1}
    Run #3: ActivityRecord{7f2d805 u0 com.tencent.mm/.ui.LauncherUI t3190}
  TaskRecord{34a386a #3238 A=.AppBrandUI2 U=0 StackId=1 sz=1}
    Run #2: ActivityRecord{16cfede u0 com.tencent.mm/.plugin.appbrand.ui.AppBrandUI2 t3238}
  TaskRecord{7ade2d1 #3237 A=.AppBrandUI U=0 StackId=1 sz=1}
    Run #1: ActivityRecord{ccfd8ae u0 com.tencent.mm/.plugin.appbrand.ui.AppBrandUI t3237}
  ...
複製代碼

能夠發現這裏的#3是微信主Activity,四、二、1都是我開的小程序,且位於不一樣的任務棧中,Activity名稱都是AppBrandUI+數字的形式。 而後再看看其餘關鍵信息(這裏我單獨篩出來):android

packageName=com.tencent.mm processName=com.tencent.mm
taskAffinity=com.tencent.mm

packageName=com.tencent.mm processName=com.tencent.mm:appbrand3
taskAffinity=.AppBrandUI3

packageName=com.tencent.mm processName=com.tencent.mm:appbrand2
taskAffinity=.AppBrandUI2

packageName=com.tencent.mm processName=com.tencent.mm:appbrand
taskAffinity=.AppBrandUI
複製代碼

很簡單,和咱們平時實現多進程差很少,說明是給Activity設置了process屬性。git

思考

轉念一想,小程序那麼多,難道這些不一樣後綴的Activity都寫死在代碼裏嗎? 顯然不能這麼幹,只能兩種途徑能夠達成目的:github

  • 不在Manifest裏面靜態註冊Activity,使用相似Hook的方式動態建立進程和Activity
  • 動靜結合,設計一個Activity池,在本地寫死有限數量的Activity,經過複用的方式承載小程序

對於第一種,我查閱了一些資料,理論上講是能夠作到的,涉及到NDK開發,須要咱們對AMS的源碼很熟悉,不走常規流程啓動Activity,且小程序是多進程的,可能還須要手動fork進程。 這種方式顯然具備較大的風險,屬於黑科技範疇,並且谷歌官方是不推薦的,微信做爲十幾億用戶的常駐App,幾乎不太可能使用這一方案。面試

那麼只剩第二種了,預先在本地寫死n個同樣的Activity(固然也能夠經過繼承形式),同時在Manifest中註冊好。 而後打開一個小程序就佔用一個Activity,當打開第n+1個小程序時,覆蓋第1個小程序所在的Activity,這樣就至關於第1個小程序被頂掉了。shell

分析到此,就很明顯了,若是真的是第二種方案,那麼小程序就不能無限數量地打開咯?果斷打開微信試了一下,果真,最多隻能開5個!當你啓動第6個小程序時,第1個就被銷燬了。 其實這也是符合咱們上述預期的,每一個小程序的進程不同,taskAffinity也不同,類名也不同。原生API是不支持動態設置taskAffinity和進程名的。小程序

簡單實現

小程序所在的Activity:微信小程序

public class SmallActivity extends AppCompatActivity {

    public static class Small0 extends SmallActivity {}
    public static class Small1 extends SmallActivity {}
    public static class Small2 extends SmallActivity {}
    public static class Small3 extends SmallActivity {}
    public static class Small4 extends SmallActivity {}

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_small);
        
        // 動態地給小程序Activity設置名稱和圖標,下面代碼只是舉例,實際信息確定是動態獲取的
        // 因爲iconRes這個構造參數的API 28才加入的,因此建議區分版本
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            int iconRes = 0; // 這裏應該是小程序圖標的資源索引
            setTaskDescription(new ActivityManager.TaskDescription("小程序名", iconRes));
        } else {
            Bitmap iconBmp = null; // 這裏應該是小程序圖標的bitmap
            setTaskDescription(new ActivityManager.TaskDescription("小程序名", iconBmp));
        }
    }
}
複製代碼

Manifest:bash

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.ysy.smallapp">

    <application ...>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".SmallActivity$Small0" android:label="Small0" android:launchMode="singleTask" android:process=":Small0" android:taskAffinity=".Small0" />

        <activity android:name=".SmallActivity$Small1" android:label="Small1" android:launchMode="singleTask" android:process=":Small1" android:taskAffinity=".Small1" />

        <activity android:name=".SmallActivity$Small2" android:label="Small2" android:launchMode="singleTask" android:process=":Small2" android:taskAffinity=".Small2" />

        <activity android:name=".SmallActivity$Small3" android:label="Small3" android:launchMode="singleTask" android:process=":Small3" android:taskAffinity=".Small3" />

        <activity android:name=".SmallActivity$Small4" android:label="Small4" android:launchMode="singleTask" android:process=":Small4" android:taskAffinity=".Small4" />

    </application>

</manifest>
複製代碼

具體的複用邏輯這裏暫時就這樣簡單地實現了,實際狀況確定比此複雜:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val edtText = findViewById<EditText>(R.id.edt_main)

        findViewById<View>(R.id.btn_main).setOnClickListener {
            startActivity(Intent().apply {
                val id = edtText.text.toString().toInt() % 5
                setClassName(this@MainActivity, "com.ysy.smallapp.SmallActivity\$Small$id")
            })
        }
    }
}
複製代碼

完整源碼: github.com/ysy950803/S…

相關文章
相關標籤/搜索