Android Notification 通知详解(兼容Android O)

 

当应用程序在后台运行,希望向用户发出一些提示学习,就需要借助Notification(通知)来实现。在发出一条通知后,手机最上方的状态栏会显示一个通知的图标,下拉状态栏后就可以看到通知的详细内容。

Notification是一种具有全局效果的通知,程序一般通过NotificationManager服务来发送Notification

一.基本用法

      通知可以在活动里面创建,也可以在广播接收器里面创建,也可以在服务里面创建

2.1 创建一个NotificationManager

NotificationManager类是一个通知管理器类,这个对象是由系统维护的服务,是以单例模式的方式获得,所以一般并不直接实例化这个对象。在Activity中,可以使用Activity.getSystemService(String)方法获取NotificationManager对象,Activity.getSystemService(String)方法可以通过Android系统级服务的句柄,返回对应的对象。在这里需要返回NotificationManager,所以直接传递Context.NOTIFICATION_SERVICE即可。

创建一个NotificationManager来对通知进行管理。通过调用Context.getSystemService(String s)方法获取到NotificationManager实例对象,字符串s参数用于确定获取系统的哪个服务,这里传入Context.NOTIFICATION_SERVICE即可,如下所示:

NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

2.2 使用Builder构造器来创建Notification对象

使用NotificationCompat类的Builder构造器来创建Notification对象,可以保证程序在所有的版本上都能正常工作。Android8.0新增了通知渠道这个概念,如果没有设置,则通知无法在Android8.0的机器上显示

           NotificationCompat.Builder(Context context, String channelId)

 

//创建Notification,传入Context和channelId
Notification notification = new NotificationCompat.Builder(this, "chat")
                        .setAutoCancel(true)
                        .setContentTitle("收到聊天消息")
                        .setContentText("今天晚上吃什么")
                        .setWhen(System.currentTimeMillis())
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                        .setContentIntent(pendingIntent)
                        //在build()方法之前还可以添加其他方法
                        .build();

 

虽然通知中提供了各种属性的设置,但是一个通知对象,有几个属性是必须要设置的,其他的属性均是可选的,必须设置的属性如下:

  • 小图标,使用setSamllIcon()方法设置。
  • 标题,使用setContentTitle()方法设置。
  • 文本内容,使用setContentText()方法设置。 

Builder构造器其中一些方法说明如下:

setAutoCancel(boolean boolean)    设置点击通知后自动清除通知
setContent(RemoteView view)       设置自定义通知
setContentTitle(String string)    设置通知的标题内容
setContentText(String string)    设置通知的正文内容
setContentIntent(PendingIntent intent)    设置点击通知后的跳转意图
setWhen(long when)        设置通知被创建的时间
setSmallIcon(int icon)    设置通知的小图标  注意:只能使用纯alpha图层的图片进行设置,小图标会显示在系统状态栏上

setLargeIcon(Bitmap icon)    设置通知的大图标    下拉系统状态栏时就能看见

setPriority(int pri)    设置通知的重要程度
setStyle(Style style)    设置通知的样式    比如设置长文字、大图片等等

setVisibility(int defaults)    设置默认
setLight(int argb, int  onMs, int offMs)    设置呼吸闪烁效果
setSound(Uri sound)            设置通知音效
setVibrate(long[] pattern)     设置震动效果,数组包含手机静止时长和震动时长
                            (下标0代表手机静止时长   下标1代表手机整的时长       
                            下标2代表手机静止时长    下标3,4,5.......以此类推
            还需要在AndroidManifest.xml中声明权限:
            )

setColor(int argb)                设置通知栏颜色
setCategory(String category)    设置通知类别
setFullScreenIntent(PendingIntent intent, boolean b)    设置弹窗显示


其中setPriority(int pri)方法参数一共有5个常量值可选,调用NotificationCompat的常量值,如下所示:

public class NotificationCompat {
    .......
    //默认的重要程度,和不设置效果是一样的
    public static final int PRIORITY_DEFAULT = 0;
 
    //最低的重要程度,系统可能只会在特定的场合显示这条通知
    public static final int PRIORITY_MIN = -2;
    
    //较低的重要程度,系统可能会将这类通知缩小,或改变其显示的顺序
    public static final int PRIORITY_LOW = -1;
 
    //较高的重要程度,系统可能会将这类通知放大,或改变其显示的顺序
    public static final int PRIORITY_HIGH = 1;
 
    //最高的重要程度,表示这类通知消息必须让用户看到,甚至做出响应
    public static final int PRIORITY_MAX = 2;
 
 
}

注意:当设置最高重要程度后,其显示效果和QQ发送好友消息一样,如果正在其他APP内,消息会显示在屏幕上让用户看见

setDefaults

setDefaults(NotificationCompat.DEFAULT_VIBRATE) ;    // 添加默认震动提醒

setDefaults(NotificationCompat.DEFAULT_SOUND) ;     // 添加默认声音提醒

setDefaults(NotificationCompat.DEFAULT_LIGHTS) ;     // 添加默认闪光灯提醒

setDefaults(NotificationCompat.DEFAULT_ALL) ;           // 添加默认以上三种提醒

2.3 调用NotificationManager的notify(int id, Notification notification)让通知显示

notificationManager.notify(1, notification);

 notify()方法接收两个参数,其中id表示每个通知所指定的id,要不一样。代码如下:

          使用此方法前,需要将NotificationChannel(通知渠道创建出来)

1.4 让通知从状态栏消失方法

有两种方法,一种是上面创建Notification时,添加setAutoCancel(true),另外一种就是通过动态代码方式,NotificationManager.cancel(int id),如下:

//传入对应通知的id
notificationManager.cancel(1);

//取消所有通知
notificationManager.cancelAll();

二.NotificationChannel(通知渠道)

2.1 概述

       从Android 8.0系统开始,Google引入了通知渠道这个概念。

       什么是通知渠道呢?顾名思义,就是每条通知都要属于一个对应的渠道。每个App都可以自由地创建当前App拥有哪些通知渠道,但是这些通知渠道的控制权都是掌握在用户手上的。用户可以自由地选择这些通知渠道的重要程度,是否响铃、是否振动、或者是否要关闭这个渠道的通知。

      即NotificationChannel 其实是把 Notification 分了个类别,设置不同优先级,开关之类的。如果你的 app 适配了的话,用户可以关掉不喜欢的通知,以提高用户体验。

       拥有了这些控制权之后,用户就再也不用害怕那些垃圾推送消息的打扰了,因为用户可以自主地选择自己关心哪些通知、不关心哪些通知。举个具体的例子,我希望可以即时收到支付宝的收款信息,因为我不想错过任何一笔收益,但是我又不想收到支付宝给我推荐的周围美食,因为我没钱只吃得起公司食堂。这种情况,支付宝就可以创建两种通知渠道,一个收支,一个推荐,而我作为用户对推荐类的通知不感兴趣,那么我就可以直接将推荐通知渠道关闭,这样既不影响我关心的通知,又不会让那些我不关心的通知来打扰我了。

      对于每个App来说,通知渠道的划分是非常需要仔细考究的,因为通知渠道一旦创建之后就不能再修改了,因此开发者需要仔细分析自己的App一共有哪些类型的通知,然后再去创建相应的通知渠道。

2.2 创建NotificationChannel

public class NotificationUtil {
    private static final int NOTIFICATION_MUSIC_ID = 10000;
    private static NotificationManager notificationManager;
    ......
    
    //初始化NotificationManager
    private static void initNotificationManager(Context context){
        if (notificationManager == null){
            notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
 
        }
        //判断是否为8.0以上:Build.VERSION_CODES.O为26
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //创建通知渠道ID
            String channelId = "musicNotification";
            //创建通知渠道名称
            String channelName = "音乐播放器通知栏";
            //创建通知渠道重要性
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            createNotificationChannel(context, channelId, channelName, importance);
        }
    }
 
    //创建通知渠道
    @TargetApi(Build.VERSION_CODES.O)
    private static void createNotificationChannel(Context context, String channelId, String channelName, int importance) {
        NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
        //channel有很多set方法
        ......
        //为NotificationManager设置通知渠道
        notificationManager.createNotificationChannel(channel);
    }
 
}

示例:

public static String createNotificationChannel(Context context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        channelId:全局唯一,长度不能过长
        channelName:渠道名称,显示在通知栏列表 
        channelDescription: 设置描述 最长30字符
        importance:重要等级,等级不同在手机桌面上展示的顺序不同
 
        NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, channelImportance);
        notificationChannel.setDescription(channelDescription);
        // 允许通知使用震动,默认为false
        notificationChannel.enableVibration(true);
        // 设置显示模式
        notificationChannel.setLockscreenVisibility(NotificationCompat.VISIBILITY_PUBLIC);
 
        NotificationManager notificationManager =
                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.createNotificationChannel(notificationChannel);
 
        return channelId;
    } else {
        return null;
    }
}

说明:这里传入的channelId要和创建的通知channelId一致,才能为指定通知建立通知渠道

1)NotificationChannel的方法列表

getId() 获取ChannelId
enableLights(boolean boolean) 是否开启指示灯(是否在桌面icon右上角展示小红点)
setLightColor() 设置指示灯颜色
enableVibration() 是否开启整的
setVibrationPattern()  设置震动频率
setImportance() 设置频道重要性
setSound()  设置声音
getSound()  获取声音
setGroup() 设置 ChannleGroup
getGroup()  得到 ChannleGroup
setBypassDnd()   设置绕过免打扰模式
canBypassDnd()   检测是否绕过免打扰模式
getName()    获取通知渠道名称
setLockScreenVisibility() 设置是否应在锁定屏幕上显示此频道的通知
getLockscreenVisibility()    检测是否应在锁定屏幕上显示此频道的通知
setShowBadge()   设置是否显示角标
canShowBadge()     检测是否显示角标

2)重要程度

数值越高,提示权限就越高,最高的支持发出声音和悬浮通知,如下所示

public class NotificationManager {
    ......
    public static final int IMPORTANCE_DEFAULT = 3;
    public static final int IMPORTANCE_HIGH = 4;
    public static final int IMPORTANCE_LOW = 2;
    public static final int IMPORTANCE_MAX = 5;
    public static final int IMPORTANCE_MIN = 1;
    public static final int IMPORTANCE_NONE = 0;
    public static final int IMPORTANCE_UNSPECIFIED = -1000;
 
}

3)发出通知

     调用NotificationManager的notify()方法即可

4)删除NotificationChannel

     调用NotificationManager的deleteNotificationChannel(int chatChannelId)即可。
 

三.PendingIntent

3.1 概述 

对于一个通知而言,它显示的消息是有限的,一般仅用于提示一些概要信息。但是一般简短的消息,并不能表达需要告诉用户的全部内容,所以需要绑定一个意图,当用户点击通知的时候,调用一个意图展示出一个Activity用来显示详细的内容。而Notification中,并不使用常规的Intent去传递一个意图,而是使用PendingIntent。 
        Intent和PendingIntent的区别:PendingIntent可以看做是对Intent的包装,通过名称可以看出PendingIntent用于处理即将发生的意图,而Intent用来用来处理马上发生的意图。而对于通知来说,它是一系统级的全局通知,并不确定这个意图被执行的时间。当在应用外部执行PendingIntent时,因为它保存了触发应用的Context,使得外部应用可以如在当前应用中一样,执行PendingIntent里的Intent,就算执行的时候响应通知的应用已经被销毁了,也可以通过存在PendingIntent里的Context照常执行它,并且还可以处理Intent说带来的额外信息。 

      因此可以将PendingIntent看做是延迟执行的Intent。

3.2 创建PendingIntent

PendingInteng.getBroadcast(contex, requestCode, intent, flags) 
PendingInteng.getService(contex, requestCode, intent, flags) 
PendingInteng.getActivity(contex, requestCode, intent, flags) 
PendingInteng.getActivities(contex, requestCode, intent, flags) 

其中flags属性参数用于确定PendingIntent的行为: 
FLAG_ONE_SHOT: 表示返回的PendingIntent仅能执行一次,执行完后自动消失 
FLAG_NO_CREATE: 表示如果描述的PendingIntent不存在,并不创建相应的PendingIntent,而是返回NULL 
FLAG_CANCEL_CURRENT: 表示相应的PendingIntent已经存在,则取消前者,然后创建新的PendingIntent 
FLAG_UPDATE_CURRENT: 表示更新的PendingIntent,如果构建的PendingIntent已经存在,则替换它,常用。

获取到PendingIntent实例后,通过Builder构造器的setContentIntent(PendingIntent intent)方法,构建一个PendingIntent。

示例:

Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
 
Notification notification = new NotificationCompat.Builder(this, "chat")
                        ......
                        .setContentIntent(pendingIntent)
                        .build();
manager.notify(1, notification);

四.使用RemoteViews自定义Notification

和Toast一样,通知也可以使用自定义的XML来自定义样式,但是对于通知而言,因为它的全局性,并不能简单的通过inflate膨胀出一个View,因为可能触发通知的时候,响应的App已经关闭,无法获取当指定的XML布局文件。所以需要使用单独的一个RemoteViews类来操作。

  RemoteViews,描述了一个视图层次的结构,可以显示在另一个进程。层次结构也是从布局文件中“膨胀”出一个视图,这个类,提供了一些基本的操作求改其膨胀的内容。

  RemoteViews提供了多个构造函数,一般使用RemoteViews(String packageName,int layoutId)。第一个参数为包的名称,第二个为layout资源的Id。当获取到RemoteViews对象之后,可以使用它的一系列setXxx()方法通过控件的Id设置控件的属性。最后使用NotificationCompat.Builder.setContent(RemoteViews)方法设置它到一个Notification中。

实现代码:

btnCustomNotification.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                RemoteViews contentViews = new RemoteViews(getPackageName(),
                        R.layout.custom_notification);
                //通过控件的Id设置属性
                contentViews
                        .setImageViewResource(R.id.imageNo, R.drawable.btm1);
                contentViews.setTextViewText(R.id.titleNo, "自定义通知标题");
                contentViews.setTextViewText(R.id.textNo, "自定义通知内容");

                Intent intent = new Intent(MainActivity.this,
                        ResultActivity.class);

                PendingIntent pendingIntent = PendingIntent.getActivity(
                        MainActivity.this, 0, intent,
                        PendingIntent.FLAG_CANCEL_CURRENT);
                NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
                        MainActivity.this,"noti")
                        .setSmallIcon(R.drawable.ic_launcher)
                        .setContentTitle("My notification")
                        .setTicker("new message")
                        .setAutoCancel(true)
                        .setContentIntent(pendingIntent)
                        .setContent(contentViews)
                        .build();
                NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                mNotificationManager.notify(10, mBuilder);
            }
        });

五.设定提示响应

对于有些通知,需要调用一些设备的资源,使用户能更快的发现有新通知,一般可设定的响应有:铃声、闪光灯、震动。对于这三个属性,NotificationCompat.Builder提供了三个方法设定:

  • setSound(Uri sound):设定一个铃声,用于在通知的时候响应。传递一个Uri的参数,格式为“file:///mnt/sdcard/Xxx.mp3”。
  • setLights(int argb, int onMs, int offMs):设定前置LED灯的闪烁速率,持续毫秒数,停顿毫秒数。
  • setVibrate(long[] pattern):设定震动的模式,以一个long数组保存毫秒级间隔的震动。

  大多数时候,我们并不需要设定一个特定的响应效果,只需要遵照用户设备上系统通知的效果即可,那么可以使用setDefaults(int)方法设定默认响应参数,在Notification中,对它的参数使用常量定义了,我们只需使用即可:

  • DEFAULT_ALL:铃声、闪光、震动均系统默认。
  • DEFAULT_SOUND:系统默认铃声。
  • DEFAULT_VIBRATE:系统默认震动。
  • DEFAULT_LIGHTS:系统默认闪光。

  而在Android中,如果需要访问硬件设备的话,是需要对其进行授权的,所以需要在清单文件AndroidManifest.xml中增加两个授权,分别授予访问振动器与闪光灯的权限:

 
 
 
 
Notification notification = new NotificationCompat.Builder(MainActivity.this,"noti")
                        ...
                        .setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg"))) //设置通知提示音
                        .setVibrate(new long[]{0,1000,1000,1000}) //设置振动, 需要添加权限  
                        .setLights(Color.GREEN,1000,1000)//设置前置LED灯进行闪烁, 第一个为颜色值  第二个为亮的时长  第三个为暗的时长
                        .setDefaults(NotificationCompat.DEFAULT_ALL)  //使用默认效果, 会根据手机当前环境播放铃声, 是否振动
                        .build();
                manager.notify(1,notification);

Notification.FLAG_SHOW_LIGHTS //三色灯提醒,在使用三色灯提醒时候必须加该标志符
Notification.FLAG_ONGOING_EVENT //发起正在运行事件(活动中)
Notification.FLAG_INSISTENT //让声音、振动无限循环,直到用户响应 (取消或者打开)
Notification.FLAG_ONLY_ALERT_ONCE //发起Notification后,铃声和震动均只执行一次
Notification.FLAG_AUTO_CANCEL //用户单击通知后自动消失
Notification.FLAG_NO_CLEAR //只有全部清除时,Notification才会清除 ,不清楚该通知(QQ的通知无法清除,就是用的这个。还有百度通知栏里面的搜索框也是这个)。
使用方法:在设置完属性后,设置
Notification notification = builder.build();
notification.flags = Notification.FLAG_ONLY_ALERT_ONCE;

六.设置通知重要程度

 Notification notification = new NotificationCompat.Builder(MainActivity.this)
                      ...
                        .setPriority(NotificationCompat.PRIORITY_MAX)
                        .build();
                manager.notify(1,notification);

setPriority方法接收一个整形参数用于设置这条通知的重要程度, 有五个值可以选择
PRIORITY_DEFAULT:表示默认重要程度,和不设置效果一样
PRIORITY_MIN:表示最低的重要程度。系统只会在用户下拉状态栏的时候才会显示
PRIORITY_LOW:表示较低的重要性,系统会将这类通知缩小,或者改变显示的顺序,将排在更重要的通知之后。
PRIORITY_HIGH:表示较高的重要程度,系统可能会将这类通知方法,或改变显示顺序,比较靠前
PRIORITY_MAX:最重要的程度, 会弹出一个单独消息框,让用户做出相应。

 

七.Notification视觉风格

Notification有两种视觉风格,一种是标准视图(Normal view)、一种是大视图(Big view)。标准视图在Android中各版本是通用的,但是对于大视图而言,仅支持Android4.1+的版本。

7.1.标准视图 

 从官方文档了解到,一个标准视图显示的大小要保持在64dp高,宽度为屏幕标准。标准视图的通知主体内容有一下几个:

  1. 通知标题。
  2. 大图标。
  3. 通知内容。
  4. 通知消息。
  5. 小图标。
  6. 通知的时间,一般为系统时间,也可以使用setWhen()设置。

7.2.大视图

而对于大视图(Big View)而言,它的细节区域只能显示256dp高度的内容,并且只对Android4.1+之后的设备才支持,它比标准视图不一样的地方,均需要使用setStyle()方法设定.

 setStyle()传递一个NotificationCompat.Style对象,它是一个抽象类,Android为我们提供了三个实现类,用于显示不同的场景。分别是:

  • NotificationCompat.BigPictureStyle, 在细节部分显示一个256dp高度的位图。
  • NotificationCompat.BigTextStyle,在细节部分显示一个大的文本块。
  • NotificationCompat.InboxStyle,在细节部分显示一段行文本。

  如果仅仅显示一个图片,使用BigPictureStyle是最方便的;如果需要显示一个富文本信息,则可以使用BigTextStyle;如果仅仅用于显示一个文本的信息,那么使用InboxStyle即可。

八.设置富文本信息

当我们使用setContentText的时候, 内容为很长的字符串,内容显示不全。如果产品就要显示完全的内容文本我们怎么办。可以使用setStyle()

 Notification notification = new NotificationCompat.Builder(MainActivity.this,"noti")
                      ...
                        .setStyle(new NotificationCompat.BigTextStyle().bigText("这是一段很长的文字很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长"))
                        .setBigContentTitle("标题")                
                        .build();
                manager.notify(1,notification);

我们在setStyle()方法中创建了NotificationCompat.BigTextStyle对象。这个对象就是用于封装长文本信息的,调用它的bigText()方法将文字传入就行。

九.设置带有图片消息

Notification notification = new NotificationCompat.Builder(MainActivity.this)
                      ...
                        .setStyle(new NotificationCompat.BigPictureStyle()
                                .bigPicture(BitmapFactory.decodeResource(getResources()
                                    ,R.mipmap.ic_launcher)))
                        .setBigContentTitle("标题")           
                        .build();
                manager.notify(1,notification);

十.设置行文本消息

Notification notification = new NotificationCompat.Builder(MainActivity.this)
                      ...
                        .setStyle(new NotificationCompat.InboxStyle()
                        .addLine("第一行")
                        .addLine("第二行")
                        .addLine("第三行")
                        .addLine("第四行")
                        .setBigContentTitle("标题")
                        .setSummaryText("内容描述");
                        .build();
                manager.notify(1,notification);

 

十一.进度条样式的通知

对于一个标准通知,有时候显示的消息并不一定是静态的,还可以设定一个进度条用于显示事务完成的进度。

  Notification.Builder类中提供一个setProgress(int max,int progress,boolean indeterminate)方法用于设置进度条,max用于设定进度的最大数,progress用于设定当前的进度,indeterminate用于设定是否是一个确定进度的进度条。通过indeterminate的设置,可以实现两种不同样式的进度条,一种是有进度刻度的(true),一种是循环流动的(false)。下面分别用两个示例演示:

有进度的进度条,实现代码:

manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                 builder = new NotificationCompat.Builder(MainActivity.this)
                         .setSmallIcon(R.drawable.ic_launcher)
                         .setContentTitle("Picture Download")
                         .setContentText("Download in progress");
                builder.setAutoCancel(true);
                //通过一个子线程,动态增加进度条刻度
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int incr;
                        for (incr = 0; incr <= 100; incr += 5) {
                            builder.setProgress(100, incr, false);
                            manager.notify(0, builder.build());
                            try {
                                Thread.sleep(300);
                            } catch (InterruptedException e) {
                                Log.i(TAG, "sleep failure");
                            }
                       }
                        builder.setContentText("Download complete")
                                .setProgress(0, 0, false);
                        manager.notify(0, builder.build());
                     }
                 }).start();

对于循环流动的进度条,下面是实现代码:

manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                  builder = new NotificationCompat.Builder(MainActivity.this)
                          .setSmallIcon(R.drawable.ic_launcher)
                          .setContentTitle("Picture Download")
                          .setContentText("Download in progress");
                  builder.setProgress(0, 0, true);//设置为true,表示流动
                 manager.notify(0, builder.build());
 
                 //5秒之后还停止流动
                 new Thread(new Runnable() {
                     @Override
                     public void run() {
                         try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                             e.printStackTrace();
                         }
                        builder.setProgress(100, 100, false);//设置为true,表示刻度
                         manager.notify(0, builder.build());
                     }
                 }).start();

 

 

https://www.cnblogs.com/plokmju/p/android_Notification.html

https://blog.csdn.net/qq_35507234/article/details/90676587

https://www.jianshu.com/p/cb8426620e74

https://blog.csdn.net/xyl826/article/details/94013413

https://www.cnblogs.com/stars-one/p/8368244.html

你可能感兴趣的:(android)