Android Notification和权限机制探讨

Posted wuhongqi0012

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Notification和权限机制探讨相关的知识,希望对你有一定的参考价值。

最近为了在部门内做一次小型的技术分享,深入了解了一下Notification的实现原理,以及android的权限机制。在此做个记录,文章可能比较长,没耐心的话就直接看题纲吧。


先看一下下面两张图

图一:


看到这图可能大家不太明白,这和我们的notification有什么关系,我来简单介绍一下背景,这是发生在15年NBA季后赛期间,火箭队对阵小牛队,火箭队以3:1领先,只要再赢一场就能淘汰对手,这时候火箭队的官方首席运营官发了这条官方推特。翻译一下就是 “一把枪指着小牛的队标,哼哼,只需要闭上你们额眼睛,马上就要结束了”。这条推特当时引起了很多人的转发和评论,并且推送给了所有关注相关比赛的球迷以及媒体。我们试想一下,你是一个小牛的球迷,输了比赛以后本来心情就很差,这时候手机一震,收到这条通知栏推送,你是不是会有一种强烈的被蔑视感觉。当天推特上就掀起了一阵网络争议,不仅小牛球迷,其他中立球迷也表示这条推特讽刺意味十足,已经有侮辱对手的嫌疑了。当然了,通知栏表示我不背这个锅,谁来背?第二天,这位首席运营官就被火箭官方开除了,并宣称此推特仅代表前运营官个人意见与火箭队无任何关系。


图二:





说完别人,再来说说我们自己吧。3月5日那天,群里都在讨论这条推送,本意是我们的编辑打算推一个分手相关的歌单,但是文案考虑不周全,让人误解。导致很多用户感到莫名其妙,我们试想一下,你准备与你最近交往的对象一起吃个晚餐,出门前收到这条祝我们分手的通知,你是不是感到很不爽呢。是的,在微博上随手一搜就发现有很多用户是这种不爽的感觉了。当然了我拿这个对比并没有说要炒这位编辑的鱿鱼噢。



好像偏题很远了,说这么多其实就是想说明一件事,应用程序的通知是非常重要的一环,处理的不好很可能给用户带来不好的印象,轻则吐槽,重则直接卸载。

好了好了,言归正传,我先列一下题纲吧

一、Notification的使用

二、Notification跨进程通信的源码分析

三、优雅地设计通知(7.0)

四、通知权限问题

五、安卓的权限机制(6.0)

六、总结



一、Notification的使用

目前咱们酷狗里的通知使用主要有以下三种场景

1.消息中心的通知

2.下载歌曲的通知

3.通过PlaybackService启动的通知

下面简单分析一下这三种场景的通知是如何实现的。

第一种是使用系统布局生成的普通通知样式


NotificationManagerCompat manager = NotificationManagerCompat.from(this);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentTitle()  [1]
.setLargeIcon()       	   [2]
.setContentText()  	   [3]
.setNumber()         	   [4] 
.setSmallIcon()       	   [5]
.setWhen()            	   [6]
.setContentIntent(pendingIntent);
manager.notify(tag, id, builder.build());


第二种是使用自定义的布局生成的通知样式

NotificationManagerCompat manager = NotificationManagerCompat.from(this);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setWhen()
.setSmallIcon()
.setLargeIcon()
.setContentIntent(pendingIntent);
RemoteViews remoteView = new RemoteViews(getPackageName,  R.layout.custom);
remoteView.setTextViewText(R.id.tv_title,  “通知标题”);
remoteView.setImageViewResource(R.id.iv_icon,  R.drawable.icon);
remoteView.setOnClickPendingIntent(R.id.iv_icon, pendingIntent);
builder.setContent(remoteView);
manager.notify(tag, id, builder.build());      RemoteViews不支持自定义View等复杂View

这两点的共性就是都是先初始化NotificationManagerCompat,和NotificationCompat.Builder, 再经过一系列builder设值后通过manager.notify去发送通知,不同点是普通通知直接设置界面元素的值,而自定义通知是构造了一个remoteView的自定义布局,把它设置给builder的content。自定义通知呢有一点需要注意就是,这个自定义的布局里的TextView字体的大小和颜色需要合理地配置,不然很容易在不同系统中和其他app的通知展示方式不一样,导致用户通知栏因为这个而显得不美观,甚至很突兀。那么,官方也是有给我们提供这样的解决方案:

Android 5.0之前可用:
android:style/TextAppearance.StatusBar.EventContent.Title    // 通知标题样式  
android:style/TextAppearance.StatusBar.EventContent             // 通知内容样式  

Android 5.0及更高版本:  
android:style/TextAppearance.Material.Notification.Title         // 通知标题样式  
android:style/TextAppearance.Material.Notification                  // 通知内容样式

当然了这么处理的话应该能解决绝大部分手机的通知文字样式问题,但还是有一些被优化或者说改造过的系统,仍然不兼容这样的通知样式,这时候就需要通过build()一个默认通知,然后再去获取当前系统通知的文字和颜色的方式了。这种方式,可以来看看我们代码中如何实现的。

public SystemNotification getSystemText() {
        mSystemNotification = new SystemNotification();

        try {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
            builder.setContentTitle("SLNOTIFICATION_TITLE")
                   .setContentText("SLNOTIFICATION_TEXT")
                   .setSmallIcon(R.drawable.comm_ic_notification)
                   .build();

            LinearLayout group = new LinearLayout(this);
            RemoteViews tempView = builder.getNotification().contentView;
            ViewGroup event = (ViewGroup) tempView.apply(this, group);
            recurseGroup(event);
            group.removeAllViews();
        } catch (Exception e) {
            mSystemNotification.titleColor = Color.BLACK;
            mSystemNotification.titleSize = 32;
            mSystemNotification.contentColor = Color.BLACK;
            mSystemNotification.contentSize = 24;
        }


        return mSystemNotification;
    }

    private boolean recurseGroup(ViewGroup gp) {
        for (int i = 0; i < gp.getChildCount(); i++) {
            View v = gp.getChildAt(i);
            if (v instanceof TextView) {
                final TextView text = (TextView) v;
                final String szText = text.getText().toString();
                if ("SLNOTIFICATION_TITLE".equals(szText)) {
                    mSystemNotification.titleColor = text.getTextColors().getDefaultColor();
                    mSystemNotification.titleSize = text.getTextSize();
//                    return true;
                }
                if ("SLNOTIFICATION_TEXT".equals(szText)) {
                    mSystemNotification.contentColor = text.getTextColors().getDefaultColor();
                    mSystemNotification.contentSize = text.getTextSize();
//                    return true;
                }
            }
//            if (v instanceof ImageView) {
//                final ImageView image = (ImageView) v;
//                if (image.getBackground().getConstantState().equals(getResources().getDrawable(R.drawable.comm_ic_notification))) {
//                    mSystemNotification.iconWidth = image.getWidth();
//                    mSystemNotification.iconHeight = image.getHeight();
//                }
//            }
            if (v instanceof ViewGroup) {// 如果是ViewGroup 遍历搜索
                recurseGroup((ViewGroup) gp.getChildAt(i));
            }
        }
        return false;
    }


至于具体如何实现的发送通知,我们待会再继续分析。


而第三种通知比较特殊,是用service.startForground(notification)的方式生成的通知。

我们酷狗启动的时候就会在通知栏生成一个可以控制播放的通知,这个通知就是playbackdservice在启动的时候生成的。

Notification notification = new Notification();
notification.icon = R.drawable.icon;
notification.flags = mFlag;
notification.contentView = mContentView;
notification.contentIntent = pendingIntent
mService.startForeground(id, notification);

这个方法的注解是这样的:Make this service run in the foreground, supplying the ongoing notification to be shown to the user while in this state.By default services are background, meaning that if the system needs to kill them to reclaim more memory (such as to display a large page in a web browser), they can be killed without too much harm.  You can set this flag if killing your service would be disruptive to the user, such as if your service is performing background music playback, so the user would notice if their music stopped playing.

二、Notification跨进程通信的源码分析