Android M Launcher3監(jiān)聽packages變化實(shí)現(xiàn)過程

前言

android L開始launcher3對(duì)于package變化的監(jiān)聽發(fā)生了改變隙笆,M之前都是通過PackageChangedReceiver(PackageChangedReceiver.java)這個(gè)廣播接收器來監(jiān)聽的。但是從L開始我們發(fā)現(xiàn)LauncherAppsCompat的實(shí)現(xiàn)類有兩個(gè):LauncherAppsCompatV16和LauncherAppsCompatVL漂坏。在LauncherAppsCompatV16中還是通過廣播(PackageMonitor extends BroadcastReceiver)來監(jiān)聽packages變化摸屠。而在 LauncherAppsCompatVL并沒有發(fā)現(xiàn)有廣播接收器谓罗,那么它到底是如何實(shí)現(xiàn)packages的監(jiān)聽的呢?

LauncherApps的初始化

我們看到在LauncherAppsCompatVL初始化時(shí)季二,通過getSystemService("launcherapps")得到一個(gè)mLauncherApps的系統(tǒng)服務(wù)檩咱。代碼如下:

LauncherAppsCompatVL(Context context) {
        super();
        mLauncherApps = (LauncherApps) context.getSystemService("launcherapps");
    }

看到getSystemService我們可以知道m(xù)LauncherApps是一個(gè)系統(tǒng)服務(wù),那這個(gè)系統(tǒng)服務(wù)到底是何方神圣呢胯舷?
LauncherApps.java位于frameworks/base/core/java/android/content/pm目錄下刻蚯,我們首先看下它的初始化。

    public LauncherApps(Context context, ILauncherApps service) {
        mContext = context;
        mService = service;
        mPm = context.getPackageManager();
    }

在LauncherApps的構(gòu)造方法中重要做三個(gè)初始化:上下文mContext桑嘶、ILauncherApps及獲取PackageManager對(duì)象炊汹。我們重點(diǎn)關(guān)注mService是如何實(shí)現(xiàn)的,它是如何做到package的監(jiān)聽的呢逃顶?

ILauncherApps的實(shí)現(xiàn)

從上文我們可以知道LauncherApps是作為客戶端通過ILauncherApps獲取服務(wù)端package變化狀態(tài)讨便,那么ILauncherApps這個(gè)接口定義了些什么呢,它又是如何實(shí)現(xiàn)對(duì)package的監(jiān)聽呢以政?我們繼續(xù)往下分析霸褒,首先看下ILauncherApps定義了什么方法:

interface ILauncherApps {
    void addOnAppsChangedListener(in IOnAppsChangedListener listener);
    void removeOnAppsChangedListener(in IOnAppsChangedListener listener);
    List<ResolveInfo> getLauncherActivities(String packageName, in UserHandle user);
    ResolveInfo resolveActivity(in Intent intent, in UserHandle user);
    void startActivityAsUser(in ComponentName component, in Rect sourceBounds,
            in Bundle opts, in UserHandle user);
    void showAppDetailsAsUser(in ComponentName component, in Rect sourceBounds,
            in Bundle opts, in UserHandle user);
    boolean isPackageEnabled(String packageName, in UserHandle user);
    boolean isActivityEnabled(in ComponentName component, in UserHandle user);
}

我們看到在這個(gè)接口中有addOnAppsChangedListener和removeOnAppsChangedListener這兩個(gè)方法及其他一些方法(我們暫時(shí)不關(guān)心),我們只關(guān)心addOnAppsChangedListener和removeOnAppsChangedListener這兩個(gè)方法盈蛮,通過名字我們可以猜測(cè)這應(yīng)該就是我們要找的package監(jiān)聽器的添加和刪除方法
傲霸。那么接下來我們來看下ILauncherApps接口的實(shí)現(xiàn)。其實(shí)現(xiàn)類LauncherAppsImpl是在LauncherAppsService的內(nèi)部類,代碼路徑是frameworks/base/services/core/java/com/android/server/pm/,LauncherAppsService是繼承自SystemService昙啄,主要用來處理適配到當(dāng)前配置文件(管理不同的user)的launcher發(fā)出的請(qǐng)求及回調(diào)。

class LauncherAppsImpl extends ILauncherApps.Stub {
    ...//省略不必要的代碼
    public void addOnAppsChangedListener(IOnAppsChangedListener listener) throws RemoteException {
            synchronized (mListeners) {
                if (DEBUG) {
                    Log.d(TAG, "Adding listener from " + Binder.getCallingUserHandle());
                }
                if (mListeners.getRegisteredCallbackCount() == 0) {
                    if (DEBUG) {
                        Log.d(TAG, "Starting package monitoring");
                    }
                    startWatchingPackageBroadcasts();
                }
                mListeners.unregister(listener);
                mListeners.register(listener, Binder.getCallingUserHandle());
            }
        }
         public void removeOnAppsChangedListener(IOnAppsChangedListener listener)
                throws RemoteException {
            synchronized (mListeners) {
                if (DEBUG) {
                    Log.d(TAG, "Removing listener from " + Binder.getCallingUserHandle());
                }
                mListeners.unregister(listener);
                if (mListeners.getRegisteredCallbackCount() == 0) {
                    stopWatchingPackageBroadcasts();
                }
            }
        }
        ...//省略不必要的代碼
}

在addOnAppsChangedListener方法中寸五,當(dāng)當(dāng)前注冊(cè)的Callback個(gè)數(shù)為0時(shí)梳凛,會(huì)調(diào)用startWatchingPackageBroadcasts()方法,也就是啟動(dòng)一個(gè)廣播監(jiān)聽梳杏,我們來看下這個(gè)廣播監(jiān)聽是如何實(shí)現(xiàn)的韧拒。

廣播監(jiān)聽

startWatchingPackageBroadcasts方法的具體實(shí)現(xiàn):

 private void startWatchingPackageBroadcasts() {
            mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
        }

在startWatchingPackageBroadcasts方法中使用了成員變量mPackageMonitor的register方法來實(shí)現(xiàn)廣播注冊(cè)。mPackageMonitor是LauncherAppsService的一個(gè)私有內(nèi)部類十性,繼承自PackageMonitor叛溢,mPackageMonitor的register方法來自父類PackageMonitor。

//廣播的intent過濾
 static {
        sPackageFilt.addAction(Intent.ACTION_PACKAGE_ADDED);
        sPackageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED);
        sPackageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED);
        sPackageFilt.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
        sPackageFilt.addAction(Intent.ACTION_PACKAGE_RESTARTED);
        sPackageFilt.addDataScheme("package");
        sNonDataFilt.addAction(Intent.ACTION_UID_REMOVED);
        sNonDataFilt.addAction(Intent.ACTION_USER_STOPPED);
        sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
        sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
 }
 //PackageMonitor的register方法實(shí)現(xiàn)
 public void register(Context context, Looper thread, UserHandle user,
            boolean externalStorage) {
        if (mRegisteredContext != null) {
            throw new IllegalStateException("Already registered");
        }
        mRegisteredContext = context;
        if (thread == null) {
            mRegisteredHandler = BackgroundThread.getHandler();
        } else {
            mRegisteredHandler = new Handler(thread);
        }
        if (user != null) {
            context.registerReceiverAsUser(this, user, sPackageFilt, null, mRegisteredHandler);
            context.registerReceiverAsUser(this, user, sNonDataFilt, null, mRegisteredHandler);
            if (externalStorage) {
                context.registerReceiverAsUser(this, user, sExternalFilt, null,
                        mRegisteredHandler);
            }
        } else {
            context.registerReceiver(this, sPackageFilt, null, mRegisteredHandler);
            context.registerReceiver(this, sNonDataFilt, null, mRegisteredHandler);
            if (externalStorage) {
                context.registerReceiver(this, sExternalFilt, null, mRegisteredHandler);
            }
        }
    }

至此我們已經(jīng)明白了L及以上版本的launcher3其實(shí)也是通過廣播監(jiān)聽來實(shí)現(xiàn)監(jiān)聽package的變化的劲适。那么這樣做的優(yōu)勢(shì)是什么呢楷掉?在我看來主要是添加對(duì)不同用戶的監(jiān)聽, 從上邊的代碼我們就可已看出在注冊(cè)監(jiān)聽的時(shí)候是有參數(shù)user的霞势。而L之前的廣播監(jiān)聽是沒有這樣做烹植,也就無法達(dá)到對(duì)不同user的精確監(jiān)聽。

當(dāng)我們監(jiān)聽到有package變化的時(shí)候愕贡,Launcher是如何實(shí)現(xiàn)圖標(biāo)的添加草雕、刪除等操作的呢?我們繼續(xù)回到LauncherAppsImpl的addOnAppsChangedListener和removeOnAppsChangedListener方法固以,我們可以看到這兩個(gè)方法的參數(shù)IOnAppsChangedListener也是一個(gè)AIDL定義的一個(gè)接口墩虹,那么這個(gè)接口到底干了什么呢?

IOnAppsChangedListener接口的實(shí)現(xiàn)

首先我們先看下這個(gè)接口中定義了哪些方法:

oneway interface IOnAppsChangedListener {
    void onPackageRemoved(in UserHandle user, String packageName);
    void onPackageAdded(in UserHandle user, String packageName);
    void onPackageChanged(in UserHandle user, String packageName);
    void onPackagesAvailable(in UserHandle user, in String[] packageNames, boolean replacing);
    void onPackagesUnavailable(in UserHandle user, in String[] packageNames, boolean replacing);
}

我們看到它是使用oneway關(guān)鍵字定義的接口憨琳,這就表明它的所有方法都采用非阻塞式方式诫钓。在這個(gè)監(jiān)聽接口中我們找到了監(jiān)聽package變化的五個(gè)方法。那這些監(jiān)聽時(shí)如何實(shí)現(xiàn)的呢栽渴?我們接下來看下IOnAppsChangedListener的實(shí)現(xiàn)方法尖坤。
IOnAppsChangedListener的實(shí)現(xiàn)是在frameworks/base/core/java/android/content/pm/LauncherApps.java

private IOnAppsChangedListener.Stub mAppsChangedListener = new IOnAppsChangedListener.Stub() {
    public void onPackageRemoved(UserHandle user, String packageName)
                throws RemoteException {
            if (DEBUG) {
                Log.d(TAG, "onPackageRemoved " + user.getIdentifier() + "," + packageName);
            }
            synchronized (LauncherApps.this) {
                for (CallbackMessageHandler callback : mCallbacks) {
                    callback.postOnPackageRemoved(packageName, user);
                }
            }
        }
    ...//省略其他方法
}

在這個(gè)方法中我們看到它是通過CallbackMessageHandler來處理package變化的消息,CallbackMessageHandler是LauncherApps的一個(gè)內(nèi)部類闲擦,繼承自Handler慢味。具體實(shí)現(xiàn)如下所示:

private static class CallbackMessageHandler extends Handler {
        ...//省略其他代碼
        private static final int MSG_REMOVED = 2;

        private LauncherApps.Callback mCallback;

        private static class CallbackInfo {
            String[] packageNames;
            String packageName;
            boolean replacing;
            UserHandle user;
        }

        public CallbackMessageHandler(Looper looper, LauncherApps.Callback callback) {
            super(looper, null, true);
            mCallback = callback;
        }

        @Override
        public void handleMessage(Message msg) {
            if (mCallback == null || !(msg.obj instanceof CallbackInfo)) {
                return;
            }
            CallbackInfo info = (CallbackInfo) msg.obj;
            switch (msg.what) {
                ...//省略其他選擇
                case MSG_REMOVED:
                    mCallback.onPackageRemoved(info.packageName, info.user);
                    break;
            }
        }

        public void postOnPackageRemoved(String packageName, UserHandle user) {
            CallbackInfo info = new CallbackInfo();
            info.packageName = packageName;
            info.user = user;
            obtainMessage(MSG_REMOVED, info).sendToTarget();
        }
        ...//省略其他代碼

在handleMessage方法中,是通過回調(diào)的方式來調(diào)用onPackageRemoved方法的墅冷。CallbackMessageHandler的構(gòu)造方法中傳進(jìn)來的是LauncherApps.Callback纯路,也就是我們的mCallback,那么這個(gè)callback到底是在什么地方實(shí)現(xiàn)的呢寞忿?

pacakge變化的回調(diào)實(shí)現(xiàn)

我們回到Launcher3的源碼驰唬,在類LauncherAppsCompatVL中有方法addOnAppsChangedCallback,在這個(gè)方法中LauncherApps調(diào)用registerCallback,將回調(diào)注冊(cè)到CallbackMessageHandler叫编。

public void registerCallback(Callback callback, Handler handler) {
        synchronized (this) {
            if (callback != null && findCallbackLocked(callback) < 0) {
                boolean addedFirstCallback = mCallbacks.size() == 0;
                addCallbackLocked(callback, handler);
                if (addedFirstCallback) {
                    try {
                        mService.addOnAppsChangedListener(mAppsChangedListener);
                    } catch (RemoteException re) {
                    }
                }
            }
        }
    }

那么LauncherAppsCompatVL的addOnAppsChangedCallback是在什么時(shí)候調(diào)用的呢辖佣?在LauncherAppState的構(gòu)造方法中有調(diào)用addOnAppsChangedCallback,參數(shù)是LauncherModel搓逾。

//LauncherAppsState構(gòu)造方法中調(diào)用addOnAppsChangedCallback
LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);

LauncherModel實(shí)現(xiàn)了LauncherAppsCompat.OnAppsChangedCallbackCompat的接口卷谈。

    //LauncherAppsCompat.OnAppsChangedCallbackCompat的接口
    public interface OnAppsChangedCallbackCompat {
        void onPackageRemoved(String packageName, UserHandleCompat user);
        void onPackageAdded(String packageName, UserHandleCompat user);
        void onPackageChanged(String packageName, UserHandleCompat user);
        void onPackagesAvailable(String[] packageNames, UserHandleCompat user, boolean replacing);
        void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing);
    }

通過上面的分析我們可以看到,當(dāng)package變化最終調(diào)用到的是LauncherModel中實(shí)現(xiàn)的以上接口霞篡,我們以onPackageRemoved為例來看下LauncherModel中的實(shí)現(xiàn)世蔗。

Callback.png

LauncherModel對(duì)package變化的處理

我們首先來看下LauncherModel中onPackageRemoved的實(shí)現(xiàn)。

public void onPackageRemoved(String packageName, UserHandleCompat user) {
        int op = PackageUpdatedTask.OP_REMOVE;
        enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
                user));
    }

在該方法中會(huì)初始化一個(gè)PackageUpdatedTask朗兵,這是LauncherModel中一個(gè)繼承自Runnable接口的內(nèi)部類污淋,然后通過Handler.post(Runnable)來執(zhí)行。根據(jù)傳進(jìn)來變量PackageUpdatedTask.OP_REMOVE余掖,PackageUpdatedTask會(huì)對(duì)當(dāng)前user的配置文件寸爆、IconCache、桌面圖標(biāo)等進(jìn)行清除操作浊吏。后邊我們會(huì)針對(duì)這一過程專門討論而昨。

總結(jié)

launcherapps監(jiān)聽.png

Android L及以上版本,Launcher3通過獲取launcherapps系統(tǒng)服務(wù)找田,監(jiān)聽package變化歌憨。而在系統(tǒng)服務(wù)launcherapps也是通PackageMonitor注冊(cè)廣播來進(jìn)行package的監(jiān)聽,和之前版本最大的變化其實(shí)是針對(duì)不同用戶的不同package進(jìn)行監(jiān)聽墩衙。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末务嫡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子漆改,更是在濱河造成了極大的恐慌心铃,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挫剑,死亡現(xiàn)場(chǎng)離奇詭異去扣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)樊破,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門愉棱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人哲戚,你說我怎么就攤上這事奔滑。” “怎么了顺少?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵朋其,是天一觀的道長(zhǎng)王浴。 經(jīng)常有香客問我,道長(zhǎng)梅猿,這世上最難降的妖魔是什么氓辣? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮袱蚓,結(jié)果婚禮上筛婉,老公的妹妹穿的比我還像新娘。我一直安慰自己癞松,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布入蛆。 她就那樣靜靜地躺著响蓉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哨毁。 梳的紋絲不亂的頭發(fā)上枫甲,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音扼褪,去河邊找鬼想幻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛话浇,可吹牛的內(nèi)容都是我干的脏毯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼幔崖,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼食店!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赏寇,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吉嫩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后嗅定,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體自娩,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年渠退,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了忙迁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡智什,死狀恐怖动漾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荠锭,我是刑警寧澤旱眯,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響删豺,放射性物質(zhì)發(fā)生泄漏共虑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一呀页、第九天 我趴在偏房一處隱蔽的房頂上張望妈拌。 院中可真熱鬧,春花似錦蓬蝶、人聲如沸尘分。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)培愁。三九已至,卻和暖如春缓窜,著一層夾襖步出監(jiān)牢的瞬間定续,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工禾锤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留私股,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓恩掷,卻偏偏與公主長(zhǎng)得像倡鲸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子螃成,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理旦签,服務(wù)發(fā)現(xiàn),斷路器寸宏,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,162評(píng)論 25 707
  • 一壺清茶凌霄外宁炫, 兩杯濁酒漫云端。 而立之年不識(shí)世氮凝? 焦愁之中尋樂閑羔巢。 于16年4月21日
    風(fēng)月無色閱讀 228評(píng)論 0 1
  • 江湖上沒有人知道離歌這個(gè)人,卻沒有人不知道——冷劍如鉤罩阵。 三年前竿秆,一場(chǎng)元夜事變,讓冷劍如鉤名噪江湖稿壁。 說來幽钢,離歌是...
    莫理我閱讀 654評(píng)論 0 2
  • 滿眼翩翩起舞的宮裝少女,伴樂的也古代著裝傅是,站在閣樓的包廂內(nèi)匪燕,俊明不由地嘆了幾口氣蕾羊,包廂里上曰:知已知彼,不戰(zhàn)而勝帽驯。...
    坎冬閱讀 459評(píng)論 0 2