Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(二)__原理分析

前置文章:

《Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解》

 

概況

在上一篇文章《Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解》中詳細介紹瞭NotificationListenerService的使用方法,以及在使用過程中遇到的問題和規避方案。本文主要分析NotificationListenerService實現原理,以及詳細分析在上一篇文章中提到的相關問題和產生的根本原因。在原理分析前,先看看NotificationListenerService涉及到的類以及基本作用,如圖1所示:

圖 1 NLS註冊及回調過程

通過圖1可以看到,整個通知狀態獲取分為三部分:

①. 監聽器註冊;新建一個類NotificationMonitor繼承自NotificationListenerService。

②. 系統通知管理;系統通知管理由NotificationManagerService負責。

③. 通知狀態回調;當系統通知狀態改變之後,NotificationManagerService會通知NotificationListenerService,最後再由NotificationListenerService通知其所有子類。

在整個系統中,通知管理是由NotificationManagerService完成的,NotificationListenerService隻是在通知改變時,會獲得相應的通知消息,這些消息最終會回調到NotificationListenerService的所有子類中。

NotificationListenerService啟動

NotificationListenerService雖然繼承自Service,但系統中實際上啟動的是其子類,為瞭表述方便,後文統一使用NotificationListenerService啟動來指代。其子類的啟動有三個途徑,分別是:開機啟動、接收PACKAGE相關廣播(安裝、卸載等)啟動、SettingsProvider數據變更啟動。

既然NotificationListenerService是一個service,那其子類啟動方式自然就是bindService或者startService,在SourceCode/frameworks/base/services/java/com/android/server/NotificationManagerService.java中可以找到,實際上NotificationListenerService的啟動是通過bindServiceAsUser來實現的,而bindServiceAsUser與bindService作用一致。

開機啟動

因為NotificationListenerService最終是在NotificationManagerService中啟動的,因此當系統在開機第一次啟動時,會進行NotificationManagerService初始化,之後會調用其SystemReady方法,繼而調用rebindListenerServices以及registerListenerService(),最後使用bindServiceAsUser實現NotificationListenerService的啟動。rebindListenerServices代碼如下:

 

void rebindListenerServices() {
    final int currentUser = ActivityManager.getCurrentUser();
    //獲取系統中哪些應用開啟瞭Notification access
    String flat = Settings.Secure.getStringForUser(
            mContext.getContentResolver(),
            Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
            currentUser);

    NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
    final ArrayList toAdd;

    synchronized (mNotificationList) {
        // unbind and remove all existing listeners
        toRemove = mListeners.toArray(toRemove);

        toAdd = new ArrayList();
        final HashSet newEnabled = new HashSet();
        final HashSet newPackages = new HashSet();

        // decode the list of components
        if (flat != null) {
            String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
            for (int i=0; i在該方法中將獲取系統中所有NotificationListenerService,並進行registerListenerService操作,代碼如下:
private void registerListenerService(final ComponentName name, final int userid) {
//... ...省略

Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
intent.setComponent(name);

intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
        R.string.notification_listener_binding_label);
intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
        mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0));

try {
    if (DBG) Slog.v(TAG, binding:  + intent);
    //使用bindService啟動NotificationListenerService
    if (!mContext.bindServiceAsUser(intent,
            new ServiceConnection() {
                INotificationListener mListener;
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    synchronized (mNotificationList) {
                        mServicesBinding.remove(servicesBindingTag);
                        try {
                            mListener = INotificationListener.Stub.asInterface(service);
                            NotificationListenerInfo info = new NotificationListenerInfo(
                                    mListener, name, userid, this);
                            service.linkToDeath(info, 0);
                            //service啟動成功之後將相關信息添加到mListeners列表中,後續通過該列表觸發回調
                            mListeners.add(info);
                        } catch (RemoteException e) {
                            // already dead
                        }
                    }
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                    Slog.v(TAG, notification listener connection lost:  + name);
                }
            },
            Context.BIND_AUTO_CREATE,
            new UserHandle(userid)))
    //... ...省略
    }
}

整個啟動流程如圖2所示:

 

圖 2 NLS開機啟動時序圖

廣播啟動

當系統安裝或者卸載應用的時候,也會觸發NotificationListenerService的啟動。當一個使用NotificationListenerService的應用被卸載掉後,需要在Notification access界面清除相應的選項,或者當多用戶切換時,也會更新NotificationListenerService的狀態。在NotificationManagerService中監聽瞭以下廣播:

 

Intent.ACTION_PACKAGE_ADDED
Intent.ACTION_PACKAGE_REMOVED
Intent.ACTION_PACKAGE_RESTARTED
Intent.ACTION_PACKAGE_CHANGED
Intent.ACTION_QUERY_PACKAGE_RESTART
Intent.ACTION_USER_SWITCHED

這些廣播在應用變更時由系統發出,比如安裝、卸載、覆蓋安裝應用等等。當NotificationManagerService接收這些廣播後編會調用rebindListenerServices,之後的流程就與前面一樣。啟動流程如下:

 

圖 3 NLS廣播啟動

數據庫變更啟動

在NotificationManagerService中使用瞭ContentObserver監聽SettingsProvider數據庫變化,當Notification access有更新時,會更新NotificationListenerService的狀態。例如,當用戶進入Notification access界面,手動開啟或關閉相關應用的Notification access權限時便會觸發這種啟動方式。當數據庫中NotificationListenerService關聯的信息改變後,會觸發ContentObserver的onChange方法,繼而調用update方法更新系統中NotificationListenerService的服務狀態,最後調用到rebindListenerServices中。整個流程如下:

圖 4 NLS數據庫變更啟動

NotificationListenerService啟動小結

在系統中實際上運行的是NotificationListenerService的子類,這些子類的啟動方式分為三種:開機啟動時NotificationManagerService初始化回調;接收相關廣播後執行;數據庫變更後執行。這些啟動方式歸根到底還是bindService的操作。

NotificationListenerService調用流程

前面提到瞭NotificationListenerService的啟動流程,當啟動完成之後就是調用,整個調用流程分為兩種情況,即:新增通知和刪除通知。

新增通知

當系統收到新的通知消息時,會調用NotificationManager的notify方法用以發起系統通知,在notify方法中則調用關鍵方法enqueueNotificationWithTag:

service.enqueueNotificationWithTag(......)

這裡的service是INotificationManager的對象,而NotificationManagerService繼承自INotificationManager.Stub。也就是說NotificationManager與NotificationManagerService實際上就是client與server的關系,這裡的service最終是NotificationManagerService的對象。這裡便會跳轉到NotificationManagerService的enqueueNotificationWithTag方法中,實際調用的是enqueueNotificationInternal方法。在該方法中就涉及到Notification的組裝,之後調用關鍵方法notifyPostedLocked():

 

 

private void notifyPostedLocked(NotificationRecord n) {
    final StatusBarNotification sbn = n.sbn.clone();
    //這裡觸發mListeners中所有的NotificationListenerInfo回調
    for (final NotificationListenerInfo info : mListeners) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                info.notifyPostedIfUserMatch(sbn);
            }});
    }
}

到這裡就開始準備回調瞭,因為前面通知已經組裝完畢準備顯示到狀態欄瞭,之後就需要將相關的通知消息告訴所有監聽者。繼續看到notifyPostedIfUserMatch方法:

public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
    //... ...省略
    try {
        listener.onNotificationPosted(sbn);
    } catch (RemoteException ex) {
        Log.e(TAG, unable to notify listener (posted):  + listener, ex);
    }
}

上面的listener對象是NotificationListenerInfo類的全局變量,那是在哪裡賦值的呢?還記得前面註冊NotificationListenerService的時候bindServiceAsUser,其中new瞭一個ServiceConnection對象,並在其onServiceConnected方法中有如下代碼:

 

public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mNotificationList) {
    mServicesBinding.remove(servicesBindingTag);
    try {
        //mListener就是NotificationListenerService子類的對象
        //service是INotificationListenerWrapper的對象,INotificationListenerWrapper
        //繼承自INotificationListener.Stub,是NotificationListenerService的內部類
        mListener = INotificationListener.Stub.asInterface(service);
        //使用mListener對象生成對應的NotificationListenerInfo對象
        NotificationListenerInfo info = new NotificationListenerInfo(
                mListener, name, userid, this);
        service.linkToDeath(info, 0);
        mListeners.add(info);
    } catch (RemoteException e) {
        // already dead
    }
}
}

也就是說在NotificationListenerService啟動並連接的時候,將binder對象保存到瞭NotificationListenerInfo中。這裡就得看看NotificationListenerService的onBind方法返回瞭,代碼如下:

 

 

@Override
public IBinder onBind(Intent intent) {
    if (mWrapper == null) {
        mWrapper = new INotificationListenerWrapper();
    }
    //這裡返回的是INotificationListenerWrapper對象
    return mWrapper;
}

private class INotificationListenerWrapper extends INotificationListener.Stub {
    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        try {
            //onNotificationPosted是抽象方法之一
            NotificationListenerService.this.onNotificationPosted(sbn);
        } catch (Throwable t) {
            Log.w(TAG, Error running onNotificationPosted, t);
        }
    }
    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
        try {
            //onNotificationRemoved是另一個抽象方法
            NotificationListenerService.this.onNotificationRemoved(sbn);
        } catch (Throwable t) {
            Log.w(TAG, Error running onNotificationRemoved, t);
        }
    }
}

通過以上代碼可以知道,當在notifyPostedIfUserMatch執行listener.onNotificationPosted方法時,實際上會調用到NotificationListenerService.INotificationListenerWrapper的onNotificationPosted方法。

NotificationListenerService是一個Abstract類,其中的Abstract方法是onNotificationPosted和onNotificationRemoved。當觸發NotificationListenerService.INotificationListenerWrapper的onNotificationPosted方法時,繼續調用瞭NotificationListenerService.this.onNotificationPosted(sbn)。這樣會繼續調用所有NotificationListenerService子類中的onNotificationPosted方法,系統通知新增的消息便傳到瞭所有NotificationListenerService中。

從整個流程來看,新增通知的發起點是NotificationManager,處理通知則是由NotificationManagerService完成,傳輸過程是通過NotificationListenerService,最後回調方法是各個繼承自NotificationListenerService的子類。整個過程的調用時序圖如下:

圖 5 onNotificationPosted觸發流程

刪除通知

與新增通知類似的流程是刪除通知,發起點在NotificationManager,之後經由NotificationManagerService處理和NotificationListenerService傳遞,最後到達各個繼承自NotificationListenerService的子類中,隻不過最後的處理方法變成瞭onNotificationRemoved。調用時序圖下:

圖 6 onNotificationRemoved觸發流程

NotificationListenerService調用流程小結

 

簡單來看,NotificationListenerService在系統通知的消息傳遞過程中,起到瞭代理的作用。繼承自NotificationListenerService的類作為client端,真正的server端則是NotificationManagerService,由它負責整個Notification的控制與管理。NotificationManagerService將處理之後的結果通過NotificationListenerService返回給client端,最終各個client端通過onNotificationPosted和onNotificationRemoved方法拿到系統通知狀態變更的相關信息。

NotificationListenerService重點分析

前文分析瞭整個NotificationListenerService的啟動和調用,通過以上分析可以很清楚的瞭解NotificationListenerService的工作流程。在上一篇文章《Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解》中,文末分析瞭在NotificationListenerService在使用過程中的遇到的一些問題,但並沒有深究出現這些問題的根本原因,下文會對這些問題進行詳細分析。

Notification access頁面不存在

當手機上沒有安裝任何使用NotificationListenerService的應用時,系統默認不會顯示Notification access選項。隻有手機中安裝瞭使用NotificationListenerService的應用,才可以在Settings > Security > Notification access 找到對應的設置頁面。在SourceCode/packages/apps/Settings/src/com/android/settings/SecuritySettings.java中可以看到如下初始化代碼:

 

//... ...省略
mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS);
if (mNotificationAccess != null) {
    final int total = NotificationAccessSettings.getListenersCount(mPM);
    if (total == 0) {
        if (deviceAdminCategory != null) {
            //如果系統中沒有安裝使用NLS的應用則刪除顯示
            deviceAdminCategory.removePreference(mNotificationAccess);
        }
    } else {
        //獲取系統中有多少啟動瞭Notification access的應用
        final int n = getNumEnabledNotificationListeners();
        //根據啟用的數量顯示不同的Summary
        if (n == 0) {
            mNotificationAccess.setSummary(getResources().getString(
                    R.string.manage_notification_access_summary_zero));
        } else {
            mNotificationAccess.setSummary(String.format(getResources().getQuantityString(
                    R.plurals.manage_notification_access_summary_nonzero,
                    n, n)));
        }
    }
}
//... ...省略

getActiveNotifications()方法返回為null

有很多人在使用getActiveNotifications方法時返回為null,多數情況下是因為在onCreate或者在onBind中調用瞭getActiveNotifications方法。比如NotificaionMonitor extends NotificationListenerService:

 

public class NotificationMonitor extends NotificationListenerService {
@Override    
public void onCreate() {
    //getActiveNotifications();
    super.onCreate();
}

@Override
public IBinder onBind(Intent intent) {
    getActiveNotifications();
    return super.onBind(intent);
}

}

 

找到NotificationListenerService中的getActiveNotifications方法實現,代碼如下:

 

public StatusBarNotification[] getActiveNotifications() {
    try {
        //getActiveNotifications成功執行的兩個關鍵點:
        //1.getNotificationInterface方法返回正常
        //2.mWrapper對象不為null
        return getNotificationInterface().getActiveNotificationsFromListener(mWrapper);
    } catch (android.os.RemoteException ex) {
        Log.v(TAG, Unable to contact notification manager, ex);
    }
    return null;
}

//通過查看可以知道,getNotificationInterface沒有問題,如果為null會進行初始化
private final INotificationManager getNotificationInterface() {
    if (mNoMan == null) {
        mNoMan = INotificationManager.Stub.asInterface(
                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
    }
    return mNoMan;
}

//如果mWrapper為null則進行初始化
@Override
public IBinder onBind(Intent intent) {
    if (mWrapper == null) {
        mWrapper = new INotificationListenerWrapper();
    }
    return mWrapper;
}

通過上面的代碼可以知道getActiveNotifications方法調用失敗的原因:

 

1. service的生命周期會先從onCraete->onBind逐步執行;

2. 此時調用getActiveNotifications方法會使用NotificationListenerService中的mWrapper對象;

3. mWrapper對象必須在NotificationMonitor完成super.onBind方法之後才會初始化;

綜上所述,當在onCreate或者onBind方法中使用getActiveNotifications方法時,會導致mWrapper沒有初始化,即mWrapper == null。解決方案可以在onCreate或者onBind方法中使用handler異步調用getActiveNotification方法,具體可參考《Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解》。

NotificationListenerService失效

如果NotificationMonitor在onCreate或onBind方法中出現crash,則該NotificationMonitor已經失效。就算修改瞭NotificationMonitor的代碼不會再crash,但NotificationMonitor還是不能收到onNotificationPosted和onNotificationRemoved回調,除非重啟手機。

這個問題是google設計上的缺陷導致,出現NotificationListenerService失效的必要條件: 在NotificationMonitor的onCreate或者onBind中出現異常,導致service crash,也就是說service還沒有完全啟動的情況下出現瞭異常導致退出。

這裡需要回到NotificationManagerService中,NotificationListenerService的註冊方法registerListenerService中:

 

private void registerListenerService(final ComponentName name, final int userid) {
    //servicesBindingTag可以理解為需要啟動的service的標簽
    final String servicesBindingTag = name.toString() + / + userid;
    //如果mServicesBinding中已經包含正在處理的service則直接return退出
    if (mServicesBinding.contains(servicesBindingTag)) {
    // stop registering this thing already! we're working on it
       return;
    }
    //將準備啟動的service標簽添加到mServicesBinding中
    mServicesBinding.add(servicesBindingTag);

    //... ...省略
    //使用bindServiceAsUser啟動service
    Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
    intent.setComponent(name);

    intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
            R.string.notification_listener_binding_label);
    intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
            mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0));

    try {
        if (DBG) Slog.v(TAG, binding:  + intent);
        if (!mContext.bindServiceAsUser(intent,
                new ServiceConnection() {
                    INotificationListener mListener;
                    @Override
                    public void onServiceConnected(ComponentName name, IBinder service) {
                        synchronized (mNotificationList) {
                            //服務成功啟動之後刪除標簽
                            mServicesBinding.remove(servicesBindingTag);
                            try {
                                mListener = INotificationListener.Stub.asInterface(service);
                                NotificationListenerInfo info = new NotificationListenerInfo(
                                        mListener, name, userid, this);
                                service.linkToDeath(info, 0);
                                mListeners.add(info);
                            } catch (RemoteException e) {
                                // already dead
                            }
                        }
                    }

                    @Override
                    public void onServiceDisconnected(ComponentName name) {
                        Slog.v(TAG, notification listener connection lost:  + name);
                    }
                },
                Context.BIND_AUTO_CREATE,
                new UserHandle(userid)))
        {
            //綁定服務失敗後刪除標簽
            mServicesBinding.remove(servicesBindingTag);
            Slog.w(TAG, Unable to bind listener service:  + intent);
            return;
        }
    } catch (SecurityException ex) {
        Slog.e(TAG, Unable to bind listener service:  + intent, ex);
        return;
    }
}
}

 

當調用registerListenerService方法時,使用瞭一個mServicesBinding的ArrayList用來記錄當前正在啟動的服務。在啟動之前會判斷當前service是否在mServicesBinding之中,如果是則表明正在執行bindServiceAsUser操作,直接退出,否則就繼續執行bindServiceAsUser流程。調用bindServiceAsUser之前會在mServicesBinding中會添加標簽,當連接成功之後也就是onServiceConnected返回後,以及綁定失敗後會在mServicesBinding中刪除標簽。

google這樣設計的目的可能是為瞭避免同一個service多次啟動,因此在執行bindServiceAsUser之前就打上標簽,當處理完成之後(onServiceConnected回調)就刪掉這個標簽,表明這個service 綁定完成。但是,如果執行bindServiceAsUser之後,NotificationMonitor在onCreate或者onBind的時候crash瞭,也就是NotificationMonitor還沒有完成啟動,因此就不會去調用onServiceConnected方法,並最終導致不會調用 mServicesBinding.remove(servicesBindingTag)方法,從而使得NotificationMonitor的標簽被一致記錄在mServicesBinding中。那麼當下一次想再次註冊該服務的時候,系統發現該服務已經在mServicesBinding中瞭,所以直接return,後面的bindServiceAsUser就不會被調用瞭。

雖然代碼已經更新,但service無法正常啟動,那麼onNotificationPosted和onNotificationRemoved的回調自然就無法使用,此時的解決辦法就隻能重啟手機,清空mServicesBinding的值。

總結

NotificationListenerService在系統通知獲取的流程中,自身並沒有啟動,而是起到瞭一個代理的作用,每一個繼承自NotificationListenerService的類,當系統通知變化後最終都會收到onNotificationPosted和onNotificationRemoved的回調。

bindService方法的返回與service是否成功啟動無關,因此才會導致NotificationListenerService失效。

最後再看一下整個NotificationListenerService的關系類圖:

圖 7 NLS關系類圖

文中圖片資源,免積分下載:戳這裡

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。