Android Notification顯示過(guò)程詳解

一、前言

最近在崩潰上報(bào)中發(fā)現(xiàn)了如下錯(cuò)誤,notification報(bào)出來(lái)的錯(cuò)誤,由于這只是在部分機(jī)型上面報(bào)出來(lái)讳苦,自己測(cè)試了幾種機(jī)型都沒能復(fù)現(xiàn),所以只有分析一下Notification的顯示過(guò)程來(lái)看一下能不能找到問(wèn)題的原因吩谦。關(guān)于這個(gè)問(wèn)題的分析我們留到最后再來(lái)看鸳谜。

12-27 01:03:49.391 2072-2072/com.test.demo:mult E/AndroidRuntime: FATAL EXCEPTION: main
      Process: com.test.demo:mult, PID: 2072
      android.app.RemoteServiceException: Bad notification posted from package com.test.demo: Couldn't expand RemoteViews for: StatusBarNotification(pkg=com.test.demo user=UserHandle{0} id=189465103 tag=null score=0: Notification(pri=0 contentView=com.test.demo/0x7f030000 vibrate=default sound=default defaults=0xffffffff flags=0x10 kind=[null]))
          at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1363)
          at android.os.Handler.dispatchMessage(Handler.java:102)
          at android.os.Looper.loop(Looper.java:136)
          at android.app.ActivityThread.main(ActivityThread.java:5017)
          at java.lang.reflect.Method.invokeNative(Native Method)
          at java.lang.reflect.Method.invoke(Method.java:515)
          at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
          at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
          at dalvik.system.NativeStart.main(Native Method)

二、Notification的基本使用

1.創(chuàng)建一個(gè)Notification式廷,并進(jìn)行基本的配置卿堂。
(1)Android SDK11以后使用builder來(lái)創(chuàng)建

Notification.Builder notificationBuilder = new Notification.Builder(JPush.mApplicationContext)
                    .setContentTitle(notificationTitle)
                    .setContentText(alert)
                    .setTicker(alert)
                    .setSmallIcon(iconRes);
Notification notification = getNotification(notificationBuilder);

(2)Android SDK11前包括11直接創(chuàng)建Notification對(duì)象即可。

Notification notification = new Notification(iconRes, alert, System.currentTimeMillis());    
notification.setLatestEventInfo(mContext,notificationTitle, alert, null);     

(3)可以通過(guò)設(shè)置contentView來(lái)自定通知的樣式懒棉。

2.顯示NotificationManager顯示Notification

NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(notifiId, notification);

三草描、Notification的顯示過(guò)程

我們調(diào)用nm.notify就會(huì)將Notification顯示出來(lái),但是這中間是什么過(guò)程呢策严?下面我們就一步一步的看看這其中發(fā)生了什么事穗慕。
首先查看NotificationManager的notify,發(fā)現(xiàn)最終調(diào)用的是另一個(gè)重載的方法妻导。

 public void notify(String tag, int id, Notification notification)
    {
        ...
        INotificationManager service = getService();
        Notification stripped = notification.clone();
        ...
        try {
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                    stripped, idOut, UserHandle.myUserId());
        } catch (RemoteException e) {
        }
    }

上面關(guān)鍵代碼就是service.enqueueNotificationWithTag逛绵,而這里的service實(shí)際上就是NotificationManagerService,查看源碼發(fā)現(xiàn)怀各,實(shí)際上最終調(diào)用的是enqueueNotificationInternal方法,其關(guān)鍵代碼入下:

 public void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid,
            final int callingPid, final String tag, final int id, final Notification notification,
            int[] idOut, int incomingUserId)
    {
     //1.基本的校驗(yàn)(顯示的消息的條數(shù)术浪、notification瓢对、和contentView是否為空)   
     ...
        mHandler.post(new Runnable() {
            @Override
            public void run() {
              ...
             if (notification.icon != 0) {
                        if (old != null && old.statusBarKey != null) {
                            //2.更新一個(gè)舊的通知
                            r.statusBarKey = old.statusBarKey;
                            long identity = Binder.clearCallingIdentity();
                            try {
                                mStatusBar.updateNotification(r.statusBarKey, n);
                            }
                            finally {
                                Binder.restoreCallingIdentity(identity);
                            }
                        } else {
                            long identity = Binder.clearCallingIdentity();
                            try {
                                //3.增加一個(gè)通知到狀態(tài)欄
                                r.statusBarKey = mStatusBar.addNotification(n);
                                if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0
                                        && canInterrupt) {
                                    mAttentionLight.pulse();
                                }
                            }
                            finally {
                                Binder.restoreCallingIdentity(identity);
                            }
                        }
                        // Send accessibility events only for the current user.
                        if (currentUser == userId) {
                            sendAccessibilityEvent(notification, pkg);
                        }

                        notifyPostedLocked(r);
                    } else {
                    }
              //4.其他配置(鈴聲、振動(dòng)等)
              ... 
            }
        });
    }

繼續(xù)查看新增的流程mStatusBar.addNotification(n)胰苏,NotificationManagerService的addNotification最終調(diào)用PhoneStatusBar的addNotification(IBinder key, StatusBarNotification notification)硕蛹,如下:

 public void addNotification(IBinder key, StatusBarNotification notification) {
        //1.創(chuàng)建通知view 
        Entry shadeEntry = createNotificationViews(key, notification);
        ...
        //2.添加到通知欄
        addNotificationViews(shadeEntry);
        ...
    }

到這里整個(gè)從創(chuàng)建到顯示的過(guò)程就完成了。 s

四硕并、android.app.RemoteServiceException問(wèn)題

根據(jù)前面的分析法焰, 接下來(lái)查看創(chuàng)建View的代碼,createNotificationViews是在父類BaseStatusBar里面定義的倔毙,如下:

 protected NotificationData.Entry createNotificationViews(IBinder key,
            StatusBarNotification notification) {
        ...
        if (!inflateViews(entry, mPile)) {
            handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
                    + notification);
            return null;
        }
        return entry;
    }

在代碼中驚訝的發(fā)現(xiàn)和異常類似的字眼Couldn't expand RemoteViews for埃仪,那看來(lái)關(guān)鍵就在inflateViews中:

 public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
        ...
        RemoteViews contentView = sbn.getNotification().contentView;
        if (contentView == null) {
            return false;
        }
        ...
        View contentViewLocal = null;
        View bigContentViewLocal = null;
        try {
            contentViewLocal = contentView.apply(mContext, adaptive, mOnClickHandler);
            if (bigContentView != null) {
                bigContentViewLocal = bigContentView.apply(mContext, adaptive, mOnClickHandler);
            }
        }
        catch (RuntimeException e) {
            final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
            Log.e(TAG, "couldn't inflate view for notification " + ident, e);
            return false;
        }
        ... 
        return true;
    }

根據(jù)inflateViews的代碼我們可以知道出現(xiàn)錯(cuò)誤的原因有兩種:
(1)contentView為null
(2)contentView.apply異常
因?yàn)閏ontentView也就是RemoteViews如果我們有定制那么就是自定義的、如果沒有自定義那么就是默認(rèn)的陕赃,所以不可能為空卵蛉,那關(guān)鍵就是RemoteViews的apply方法了,apply最終調(diào)用了performApply么库,如下:

 private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
        if (mActions != null) {
            handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
            final int count = mActions.size();
            for (int i = 0; i < count; i++) {
                Action a = mActions.get(i);
                a.apply(v, parent, handler);
            }
        }
    }

遍歷所有的action毙玻,并調(diào)用其apply,那么這個(gè)action到底是哪里來(lái)的呢,實(shí)際這些action就是我們對(duì)布局的配置廊散,如文字,圖片什么的梧疲,以設(shè)置文字為例:

   public void setTextViewText(int viewId, CharSequence text) {
        setCharSequence(viewId, "setText", text);
    }
    public void setCharSequence(int viewId, String methodName, CharSequence value) {
        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
    }

調(diào)用setTextViewText實(shí)際上是添加了一個(gè)RelectionAction允睹,查看其apply方法:

 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
            final View view = root.findViewById(viewId);
            if (view == null) return;

            Class<?> param = getParameterType();
            if (param == null) {
                throw new ActionException("bad type: " + this.type);
            }

            try {
                getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
            } catch (ActionException e) {
                throw e;
            } catch (Exception ex) {
                throw new ActionException(ex);
            }
        }

可以看到是通過(guò)反射來(lái)調(diào)用相應(yīng)的方法來(lái)進(jìn)行設(shè)置的,但是反射可能會(huì)拋出異常的幌氮,導(dǎo)致崩潰:
(1)NoSuchMethodException 找不到方法缭受,比如向一個(gè)ImageView,調(diào)用setText
(2)NullPointerException 調(diào)用的對(duì)象為空该互,由于RelectionAction中有對(duì)View的判斷米者,所以此異常不會(huì)發(fā)生。
(3)IllegalAccessException 調(diào)用的方法是私有的宇智,由于RemoteViews對(duì)外提供的方法都是控件的public的方法蔓搞,所以不會(huì)發(fā)生
(4) InvocationTargetException 調(diào)用發(fā)生異常,這個(gè)不是很確定能不能發(fā)生随橘。
通過(guò)測(cè)試發(fā)現(xiàn)(1)是可以重現(xiàn)的喂分,也就是說(shuō)布局上的錯(cuò)誤,我們調(diào)用RemoteViews的setTextViewText在一個(gè)ImageView就會(huì)生這種情況机蔗,當(dāng)然也會(huì)有其他情況蒲祈。

五甘萧、總結(jié)

根據(jù)上面的分析可以知道發(fā)生這個(gè)問(wèn)題,很有可能是布局的問(wèn)題梆掸,基本都是反射的問(wèn)題扬卷。當(dāng)然RemoteViews使用的控件是有限制的,并不是所有的控件都能使用酸钦,否則肯定會(huì)崩潰,關(guān)于哪些控件是可用的可以查看官方文檔怪得。但是我這里的問(wèn)題是某些機(jī)型會(huì)崩潰,而且我使用了自定義布局钝鸽,所以我懷疑是可能某些機(jī)型對(duì)自定義通知有限制汇恤,導(dǎo)致在RelectionAction的apply時(shí)拋出了異常,所以最后解決辦法是在nm.notify顯示通知時(shí)catch掉拔恰,然后不使用自定義的通知因谎,而使用系統(tǒng)默認(rèn)的通知。

更正:之前沒有注意颜懊,android.app.RemoteServiceException實(shí)際上是無(wú)法catch住的财岔,因?yàn)榘l(fā)生改異常時(shí),查看源碼是直接底層崩潰河爹,java層跟本捕獲不到異常匠璧,后面我會(huì)繼續(xù)研究這一塊看看有沒有別的處理辦法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咸这,隨后出現(xiàn)的幾起案子夷恍,更是在濱河造成了極大的恐慌,老刑警劉巖媳维,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酿雪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡侄刽,警方通過(guò)查閱死者的電腦和手機(jī)指黎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)州丹,“玉大人醋安,你說(shuō)我怎么就攤上這事∧苟荆” “怎么了吓揪?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)所计。 經(jīng)常有香客問(wèn)我磺芭,道長(zhǎng),這世上最難降的妖魔是什么醉箕? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任钾腺,我火速辦了婚禮徙垫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘放棒。我一直安慰自己姻报,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布间螟。 她就那樣靜靜地躺著吴旋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厢破。 梳的紋絲不亂的頭發(fā)上荣瑟,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音摩泪,去河邊找鬼笆焰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛见坑,可吹牛的內(nèi)容都是我干的嚷掠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼荞驴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼不皆!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起熊楼,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤霹娄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鲫骗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體犬耻,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年挎峦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片合瓢。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡坦胶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晴楔,到底是詐尸還是另有隱情顿苇,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布税弃,位于F島的核電站纪岁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏则果。R本人自食惡果不足惜幔翰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一漩氨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遗增,春花似錦叫惊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至饰及,卻和暖如春蔗坯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背燎含。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工宾濒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瘫镇。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓鼎兽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親铣除。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谚咬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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