Android Notification

废话

经常熬夜有三大害处:第一,记忆力越来越差;第二,数学水平下降;第四,记忆力越来越差。

是不是觉得这个段子很熟悉,没错,我在CSDN上曾经发过Notification的博客,CSDN弃用这么长时间,觉得有必要把一部分文章转移到简书来,今天这就是第一篇。在原来的基础上稍微修改一下,再整理一下头绪。

Notification在Android中使用的还是挺多的,我们公司的项目基本都用到了。这篇文章依然保持我的风格,用写Demo的方式来学习,这样记忆最深。

Demo

我们写一个小Demo,把各种类型的Notification全部展示一遍,首先看下demo截图

《Android Notification》 demo

简单看下,大概会有这么二十多种写法的Notification,下面一个一个来看。

普通通知

Android3.0是一个分水岭,在其之前构建Notification推荐使用NotificationCompate.Builder,它位于android.support.v4.app.NotificationCompat.Builder,是一个Android向下版本的兼容包,而在Android3.0之后,一般推荐使用Notification.Builder构建。本博客主要介绍的是Android4.x的开发,所以在这里使用Notification.Builder进行讲解演示。

通知一般通过NotificationManager服务发送一个Notification对象来完成通知,NotificationManager是一个重要的系统级服务,该对象位于应用程序的框架层中,应用程序可以通过它向系统发送全局的通知。使用通知的时候,需要创建一个Notification对象用来承载通知的内容,但是一般不会直接通过Notification的构造方法来得到对象,而是使用它的内部类Notification.Builder来实例化一个Builder对象,并设置通知的各项属性,最后通过Notification.Builder.builder()方法得到一个Notification对象,当获得这个Notification对象之后,就可以使用NotificationManager.notify()方法发送通知。

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

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

  • 小图标,使用setSamllIcon()方法设置。
  • 标题,使用setContentTitle()方法设置。
  • 文本内容,使用setContentText()方法设置。
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setContentTitle("普通通知")
        .setContentText("这是一条普通通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

NOTIFICATION_ID是一个自己定义值,一个id表示一个notification,如果两次发出的notification是相同的id,那就会更新之前的那一个,这是id的用处之一。id另外一个用处就是用于移除notification。

mNotificationManager.cancel(NOTIFICATION_ID);

当然除了可以用id移除notification以外,还可以通过Tag移除

mNotificationManager.cancel("tag", NOTIFICATION_ID);

或者一次移除所有notification

mNotificationManager.cancelAll();

《Android Notification》 普通通知

设置属性

最普通的notification肯定是没法满足我们的,因为既没有点击效果,展示信息也很少,所以我们要看看notification给我们提供了哪些api可以设置参数。

大小图标

setSmallIcon(R.drawable.icon_small)
setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))

当 setSmallIcon() 与 setLargeIcon() 同时存在时, smallIcon 显示在通知的右下角, largeIcon 显示在左侧;当只设置 setSmallIcon() 时, smallIcon 显示在左侧。看下图你就明白了。对于部分 ROM ,可能修改过源码,如 MIUI 上通知的大图标和小图标是没有区别的。

《Android Notification》 图标

Ticker提示语句

setTicker("来了一条设置属性通知")

在来一条notification时,默认情况通知栏上会显示一个小icon,但是不是很显眼,并且不下拉通知栏就不知道具体是来了什么消息,Ticker就可以显示一句提示语句。

《Android Notification》 Ticker

设置时间
这个可设可不设,默认也会取系统时间

setWhen(System.currentTimeMillis())

点击自动移除
这个最好还是设置成true,我自己测试的手机默认是不会自动移除的,一条notification被点击了,我们就默认为查看了,就不应该再显示。

setAutoCancel(true)

设置数量
就是右下角显示的数字

setNumber(23)

《Android Notification》 number

设置数据
这个数据主要是携带给跳转Activity用的,比如我们推送了一部小说,携带了小说id等重要信息,用户点击后可以跳转对应的小说详情去。

setExtras(new Bundle())

设置跳转
这个是最重要的属性了,没有这个属性,通知基本就没什么意思了。

Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);

setContentIntent(pendingIntent)

flag参数有四个取值

  • FLAG_CANCEL_CURRENT:如果构建的PendingIntent已经存在,则取消前一个,重新构建一个。
  • FLAG_NO_CREATE:如果前一个PendingIntent已经不存在了,将不再构建它。
  • FLAG_ONE_SHOT:表明这里构建的PendingIntent只能使用一次。
  • FLAG_UPDATE_CURRENT:如果构建的PendingIntent已经存在,则替换它,常用。

最后看下全部的代码

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setTicker("来了一条设置属性通知")
        .setContentIntent(pendingIntent)
        .setWhen(System.currentTimeMillis())
        .setNumber(23)
        .setAutoCancel(true)
        .setExtras(new Bundle())
        .setContentTitle("设置属性通知")
        .setContentText("这是一条设置属性通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

大图模式

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.head_image);

Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setContentIntent(pendingIntent)
        .setStyle(new Notification.BigPictureStyle().bigPicture(bitmap))
        .setContentTitle("大图模式通知")
        .setContentText("这是一条大图模式通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

《Android Notification》 大图

文本段模式

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);

Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setContentIntent(pendingIntent)
        .setNumber(12)
        .setStyle(new Notification.BigTextStyle()
                .setBigContentTitle("这是一段长文本")
                .setSummaryText("这是总结")
                .bigText("打南边来了个哑巴,腰里别了个喇叭;打北边来了个喇嘛,手里提了个獭犸。提着獭犸的喇嘛要拿獭犸换别着喇叭的哑巴的喇叭;别着喇叭的哑巴不愿拿喇叭换提着獭犸的喇嘛的獭犸。不知是别着喇叭的哑巴打了提着獭犸的喇嘛一喇叭;还是提着獭犸的喇嘛打了别着喇叭的哑巴一獭犸。喇嘛回家炖獭犸;哑巴嘀嘀哒哒吹喇叭"))
        .setContentTitle("长文本通知")
        .setContentText("这是一条长文本通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

《Android Notification》 文本段

文本块模式

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);

Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setContentIntent(pendingIntent)
        .setNumber(12)
        .setStyle(new Notification.InboxStyle()
                .setBigContentTitle("这是一个文本段")
                .setSummaryText("这是总结")
                .addLine("文本行111")
                .addLine("文本行222")
                .addLine("文本行333")
                .addLine("文本行444")
                .addLine("文本行555")
                .addLine("文本行666"))
        .setContentTitle("设置属性通知")
        .setContentText("这是一条文本段通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

《Android Notification》 文本块

精确进度条

这个用的多一些,展示精确的进度值,setProcess()方法第一个参数是最大进度值,第二个参数是当前进度值,步长是1,第三个参数false表示的就是精确进度条,true表示的是模糊进度条。

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder mProgressBuilder = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setProgress(100, 0, false)
        .setContentTitle("进度条通知")
        .setContentText("这是一条进度条通知");
new Thread() {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            mProgressBuilder.setProgress(100, i, false);
            mNotificationManager.notify(NOTIFICATION_ID, mProgressBuilder.build());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}.start();

《Android Notification》 progress

模糊进度条

第三个参数false表示的就是精确进度条,true表示的是模糊进度条。如果是模糊进度条,那就不存在最大值和当前值,所以前两个参数传0即可。

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setContentIntent(pendingIntent)
        .setProgress(0, 0, true)
        .setContentTitle("模糊进度条通知")
        .setContentText("这是一条模糊进度条通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

《Android Notification》 progress

震动、音效、呼吸灯

这个很简单,Android提供了三种效果:震动、音效、呼吸灯,以及它们三者的任意组合。

// 默认震动
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setContentIntent(pendingIntent)
        .setDefaults(Notification.DEFAULT_VIBRATE)
        .setContentTitle("震动通知")
        .setContentText("这是一条震动通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

// 默认音效
setDefaults(Notification.DEFAULT_SOUND)

// 默认呼吸灯
setDefaults(Notification.DEFAULT_LIGHTS)

没有图,这三个效果真没法配图。。。

《Android Notification》

还可以选择全效果,震动+音效+呼吸灯

setDefaults(Notification.DEFAULT_ALL)

自定义音效

我写demo的这个音效来自腾讯,我有一次在港式餐厅里喝咖啡,正好旁边一个腾讯的工程师,聊到我最近要写一个Notification的博客,缺少一个音效文件,他就推荐了我这个,在此非常感谢腾讯的这位朋友。

《Android Notification》

好了,不装逼了。。。是我从QQ apk里解压出来的。。。

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setContentIntent(pendingIntent)
        .setSound(Uri.parse("android.resource://com.makeunion.notificationdemo/" + R.raw.qq))
        .setContentTitle("自定义音效通知")
        .setContentText("这是一条自定义音效通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

从代码中应该也能看出来,音效文件是放在raw目录下的。

自定义震动

这里我们需要传一个long[]数组参数,这个参数真的是日了够了,我看到的解释是这样的:
第一个0表示手机静止的时长
第二个300表示手机震动的时长
第三个300表示手机静止的时长
第四个300表示手机震动的时长

所以这里表示手机先震动300毫秒,然后静止300毫秒,然后再震动300毫秒

但是,我实际的效果总是有一点出入,我自己猜想应该是notification总的提示时间是有限制的,所以会对最终效果有影响。

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
long[] vibrate = new long[]{0, 300, 300, 300};
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setContentIntent(pendingIntent)
        .setVibrate(vibrate)
        .setContentTitle("自定义震动通知")
        .setContentText("这是一条自定义震动通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

自定义呼吸灯

这个呼吸灯也是日了够了,完全看不到效果,我不知道是我用的不对,还是现在Android手机都阉割了这个功能。。。我觉得第二种可能性大些。

第一个参数表示的是颜色
第二个参数表示的是灯亮的时间
第三个参数表示的是灯灭的时间

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setContentIntent(pendingIntent)
        .setLights(0xFF0000, 1000, 500)
        .setContentTitle("自定义呼吸灯通知")
        .setContentText("这是一条自定义呼吸灯通知").build();
notification.flags = Notification.FLAG_SHOW_LIGHTS;
mNotificationManager.notify(NOTIFICATION_ID, notification);

只提示一次

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setContentIntent(pendingIntent)
        .setSound(Uri.parse("android.resource://com.makeunion.notificationdemo/" + R.raw.qq))
        .setContentTitle("无限循环通知")
        .setContentText("这是一条无限循环通知").build();
notification.flags |= Notification.FLAG_INSISTENT;
mNotificationManager.notify(NOTIFICATION_ID, notification);

慎用!!!你会听到一段疯狂的提示音。

也可以设置成只提示一次

notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE;

顶部悬浮

API21之后,Android提供了顶部悬浮显示Notification,这种显示方式的好处是更加显眼,可以直接看到通知的内容,而且不影响当前app的使用。要实现顶部悬浮效果有两种方式:

  • setFullScreenIntent
  • PRIORITY_HIGH高优先级
NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setFullScreenIntent(pendingIntent, false)
        .setSound(Uri.parse("android.resource://com.makeunion.notificationdemo/" + R.raw.qq))
        .setContentTitle("顶部悬浮通知通知")
        .setContentText("这是一条顶部悬浮通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

或者

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setPriority(Notification.PRIORITY_HIGH)
        .setSound(Uri.parse("android.resource://com.makeunion.notificationdemo/" + R.raw.qq))
        .setContentTitle("顶部悬浮通知")
        .setContentText("这是一条顶部悬浮通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

《Android Notification》

锁屏Notification

同样是API21之后,Android可以实现在锁屏状态下显示通知,对于大多数app而言,在锁屏状态显示是用户友好的,因为因为用户不用打开手机就能看到通知内容,但是对于一些敏感信息,比如短信之类的,最好不要在锁屏显示具体的信息内容。

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setContentIntent(pendingIntent)
        .setNumber(23)
        .setAutoCancel(true)
        .setVisibility(Notification.VISIBILITY_PUBLIC)
        .setContentTitle("锁屏通知")
        .setContentText("这是一条锁屏通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

《Android Notification》

Action

API20之后新增的功能,这是一个很好的功能,我们通常一个通知的作用就是展示,最多给整体设置一个pendingIntent,这样扩展性就比较差了,但是有了Action,我们就能在通知上自定义功能键了。

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon_big))
        .setFullScreenIntent(pendingIntent, false)
        .addAction(new Notification.Action(R.drawable.icon_share, "分享", pendingIntent))
        .addAction(new Notification.Action(R.drawable.icon_zan, "收藏", pendingIntent))
        .addAction(new Notification.Action(R.drawable.icon_message, "消息", pendingIntent))
        .setSound(Uri.parse("android.resource://com.makeunion.notificationdemo/" + R.raw.qq))
        .setContentTitle("action功能通知")
        .setContentText("这是一条action功能通知").build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

《Android Notification》

这里有几点需要说明:

  • Action的icon最好不要设计成有颜色的,按照MaterialDesign的规范,最好是白色内容,透明背景。如果有别的颜色,也会被着色成白色。(但是有的定制机又不会)
  • Action不要太多,3个已经足够了,太多了显示有可能出问题
  • api20才支持,旧版本使用会报错

自定义小视图

自定义视图用的还是挺多的,毕竟原生视图太丑。

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_CANCEL_CURRENT);
RemoteViews remoteViews = new RemoteViews("com.makeunion.notificationdemo", R.layout.layout_remote_view);
remoteViews.setOnClickPendingIntent(R.id.play_pause_img, pendingIntent);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setAutoCancel(true)
        .setContentIntent(pendingIntent)
        .setContent(remoteViews).build();
mNotificationManager.notify(NOTIFICATION_ID, notification);

layout布局的代码我就不贴了,很简单,没什么说的

《Android Notification》

是不是婉如一个真的音乐播放器。(原谅我最近玩抖音玩入迷了~)

自定义大视图

NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
RemoteViews remoteViews = new RemoteViews("com.makeunion.notificationdemo", R.layout.layout_remote_view);
RemoteViews bigRemoteViews = new RemoteViews("com.makeunion.notificationdemo", R.layout.layout_big_remote_view);
Notification notification = new Notification.Builder(this)
        .setSmallIcon(R.drawable.icon_small)
        .setOngoing(true)
        .setTicker("来了一条伸缩布局通知")
        .build();
notification.bigContentView = bigRemoteViews;
notification.contentView = remoteViews;
mNotificationManager.notify(NOTIFICATION_ID, notification);

《Android Notification》

这是一个大图模式和小图模式的切换,效果还不错,但是兼容性不行,在有些手机上显示效果不对,还需要继续研究。和定制机也有一定关系。

总结

好啦,这就是本期的Notification,总的来说内容比较简单,但很实用。后面还会有一系列很实用的Android文章出炉。

说到这,我想起一个很久前的故事,某日我写了一篇博客,一个心怀梦想的年轻人看了我的博客,并给我打了2块钱的赏,今后几年他事业顺利,婚姻幸福,身体健康,越来越帅,从此走上人生巅峰。

    原文作者:大公爵
    原文地址: https://www.jianshu.com/p/c0b0ed829241
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞