关于Android中通知栏的讲解及适配Android 8.0

前言

通知栏是Android系统原创的一个功能,虽然乔布斯一直认为Android系统是彻彻底底抄袭IOS的一个产品,但是通知栏确实是Android系统原创的,反而苹果在ios 5之后也加入了类似的通知栏功能。

通知栏的设计非常巧妙,他默认情况下不占用任何空间,只有当用户需要的时候用手指在状态栏上向下滑动,通知栏的内容才会显示出来,极大地解决了手机屏幕过小,内容展示区域不足的问题。

可能有的读者比较好奇那么ios 5 之前未引入通知栏功能的时候,iPhone都是怎样进行消息通知的呢?使用的就是未读角标的功能,效果图如下:
在这里插入图片描述
最后当然这一功能也被很多国产手机厂商所模仿,纷纷推出了自己的角标功能,其中很多都是通过向系统发送广播的方式来实现的,同时由于该角标功能的实现GooGle官方并没有给出统一的api,因此导致了各大手机厂商的广播标准“随意”指定,互不兼容,从而在我们开发过程中实现该角标功能的时候为了考虑兼容性而导致代码混乱,让开发者头疼。

值的高兴的是,从Android 8.0开始,Google制定了Android系统上的角标规范,同时也提供了标准api,以上让开发者头疼的问题现在也终于得到了解决,关于该角标功能的官方实现是作为8.0通知栏功能更新的一部分而添加的,在文章后面讲解8.0通知栏时我们会详细讲解,此处提及只是先让开发者粗略了解一下角标和通知栏的存在背景相关的知识。

Android 7.0适配及微技巧讲解

8.0适配之前我们先来了解一下7.0适配的一些知识,然后引出8.0适配,循序渐进方便读者理解。

Android系统上的通知栏功能随着不同版本的发布几乎一直都在做调整,尤其是7.0版本又对通知栏做了比较大的调整,这样就导致新版本的通知栏API无法兼容老的版本,这对开发者来说确实是一件比较头疼的事情,而Google官方当然也是意识到了这一点,为此Android在appcompat-v7库中提供了一个NotificationCompat的类来自动帮我们进行所有系统版本的兼容性处理。

使用NotificationCompat类创建通知栏的代码示例如下:

NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
Notification notification = builder
    .setContentTitle("这是通知标题")
    .setContentText("这是通知内容")
    .setWhen(System.currentTimeMillis())
    .setSmallIcon(R.mipmap.ic_launcher)
    .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
    .build();
manager.notify(1, notification);

可以看到,这里只是把我们平时使用的Notification.Builder改成了NotificationCompat.Builder而已,其他用法都是一模一样的,这样我们的通知就具备各种Android版本的兼容性了。

注意看一下我们给通知设置的图标,一个小图标、一个大图标,都是使用的R.mipmap.ic_launcher这张图。其实很多app都使用的这种做法,即直接拿应用程序的icon来作为通知的图标,好像这样看上去也挺合理的。

现在我使用Android 6.0系统的Nexus 5手机运行这个程序,并触发上面那段通知逻辑,效果如图下图所示:
在这里插入图片描述
可以看到,通知栏上弹出了一个通知图标。然后我们将通知栏下拉展开,效果如下图所示:
在这里插入图片描述
效果好像还不错的样子。但实际上,我现在是将项目的targetSdkVersion指定成了21以下,即低于5.0系统。如果将targetSdkVersion指定成21或者更高的话,结果可能就不乐观了:

defaultConfig {
    ....
    targetSdkVersion 23
}

这里我们将targetSdkVersion指定成了23,然后重新运行程序并触发图标逻辑,效果如下图所示:
在这里插入图片描述

恩?这是什么鬼,怎么通知图标变成白白的一个圆了。下拉之后的大图效果如下:
在这里插入图片描述

好像下拉之后的大图还算正常,不过大图的右下角也有一个白白的圆。

这到底是为什么呢?实际上,Android从5.0系统开始,对于通知栏图标的设计进行了修改。现在Google要求,所有应用程序的通知栏图标,应该只使用alpha图层来进行绘制,而不应该包括RGB图层。

说的好像很玄乎,什么叫作只使用alpha图层来进行绘制呢?其实通俗点来讲,就是让我们的通知栏图标不要带颜色就可以了。

恩?不带颜色!那图标还怎么设计?但这就不是我们程序员应该考虑的问题了,而是应该交给项目的UI设计师来想办法,但我们需要将这个设计需求清楚地告诉设计师,因为他们通常并不知道Google的各种标准和要求。

那么我们来参考一下别的程序都是怎么设计通知栏图标的,这是支付宝的通知栏图标:
在这里插入图片描述
下拉通知之后的效果是这样的:
在这里插入图片描述

然后再看一下网易新闻的通知栏图标:
在这里插入图片描述

下拉通知之后的效果是这样的:
在这里插入图片描述

可以看出,它们的通知栏小图都是没有RGB色的,图标是只有白色一种颜色,然后借助alpha图层来绘制出一个logo的样式。

因此,按着这种设计要求,我将项目的通知栏图标改成了这个样子:
在这里插入图片描述
这张图只用于替换通知的小图部分,大图仍然还是用原来的那样图就可以了。现在重新运行一下程序,效果如下图所示:
在这里插入图片描述

这样看上去效果就好多了吧?然后下拉通知栏之后的效果如下图所示:
在这里插入图片描述
这里我们来仔细观察一下这个下拉后的大图,其实前面大家应该也已经注意到了,只不过一直没提,在大图标的右下角,还有一个比较小的圆圈,在这个圆圈中嵌套着我们设置的小图标。

这个功能是系统自动附加的一个功能,并不需要我们进行任何的代码设置,可以观察一下,支付宝、网易新闻也都是有这个功能的。但是如果我们再看仔细一点,你会发现网易的图标更好看一些,因为系统给右下角的这个小圆圈默认是设置成灰色的,和我们的整体色调并不搭配,而网易则将这个小圆圈改成了红色,因此总体视觉效果更好。

那么怎样修改这个小圆圈的颜色呢?其实非常简单,只需要在NotificationCompat.Builder中再多连缀一个setColor()方法就可以了,如下所示:

Notification notification = builder
        ......
        .setColor(Color.parseColor("#EAA935"))
        .build();

现在重新运行一下程序,通知栏大图的具体效果如下图所示:
在这里插入图片描述
怎么样,现在的效果是不是更棒了,但是这里我还要给大家提个醒,上面的功能我使用Nexus手机和三星手机都测试过,结果都是正常的,但是使用小米手机测试就比较无语了,MIUI系统直接无视我们设置的大图和小图,一律使用应用程序的icon来作为通知栏图标,所以如果你是使用的小米手机,就测试不出来上述的各种效果了。其他手机的兼容性我还没有试过,不过不管怎么样,我们的代码都是要这么写的,至于那些定制过的系统该如何去解析展示,那是这些第三方厂商的事情,毕竟我们程序员也是控制不了的

当然,如果你手上只有小米手机的话,也不要绝望,还是可以使用Android模拟器来测试这个功能的。

Android 8.0 适配以及角标功能的实现

关于Android系统原创的通知栏设计,确实是一个很巧妙又很实用的功能,但是该功能却遭到了广大应用开发者的滥用,各个App都希望能抢占通知栏的空间,来尽可能地宣传和推广自己的产品。现在经常是早上一觉醒来拿起手机一看,通知栏上全是各种APP的推送,不胜其烦。
在这里插入图片描述
在这里插入图片描述
我个人虽然是Android应用开发者,但同时也是Android手机的资深用户。我已经使用了8年的Android手机,目前我对于通知栏的这种垃圾推送是零容忍的。现在每当我安装一个新的App时,我都会先到设置里面去找一找有没有推送开关,如果有的话我会第一时间把它关掉。而如果一个App经常给我推送垃圾信息却又无法关闭时,我会直接将它的通知总开关给关掉,如果还不是什么重要的App的话,那么我可能就直接将它卸载掉了。

每个开发者都只想着尽可能地去宣传自己的App,最后用户的手机就乱得跟鸡窝一样了。但是通知栏又还是有用处的,比如我们收到微信、短信等消息的时候,确实需要通知栏给我们提醒。因此分析下来,通知栏目前最大的问题就是,无法让用户对感兴趣和不感兴趣的消息进行区分。就比如说,我希望淘宝向我推送卖家发货和物流的相关消息,但是我不想收到那些打折促销或者是让我去买衣服的这类消息。那么就目前来说,是没有办法对这些消息做区分的,我要么同意接受所有消息,要么就屏蔽所有消息,这是当前通知栏的痛点。

那么在Android 8.0系统中,Google也是从这个痛点开始下手的,从Android 8.0系统开始,Google引入了 通知渠道 这个概念。

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

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

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

我们是否一定要对通知进行渠道适配?

建议:如果你将项目中的targetSdkVersion指定到了26或者更高,那么一定要适配。

首先,如果你升级了appcompat库,那么所有使用appcompat库来构建通知的地方全部都会进行废弃方法提示,如下所示:
在这里插入图片描述
上图告诉我们,此方法已废弃,需要使用带有通知渠道的方法才行。

当然,Google也并没有完全做绝,即使方法标为了废弃,但还是可以正常使用的。可是如果你将项目中的targetSdkVersion指定到了26或者更高,那么Android系统就会认为你的App已经做好了8.0系统的适配工作,当然包括了通知栏的适配。这个时候如果还不使用通知渠道的话,那么你的App的通知将完全无法弹出。因此这里给大家的建议就是,一定要适配。

那么如何进行通知渠道的适配?

首先我们使用Android Studio来新建一个项目,就叫它NotificationTest吧。

创建好项目之后,打开app/build.gradle文件检查一下,确保targetSdkVersion已经指定到了26或者更高,如下所示:

apply plugin: 'com.android.application'
android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.notificationtest"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

可以看到,这里我在创建新项目的时候默认targetSdkVersion就是26,如果你是低于26的话,说明你的Android SDK有些老了,最好还是更新一下。当然如果你懒得更新也没关系,手动把它改成26就可以了。

首先我们需要仔细分析自己的App一共有哪些类型的通知,然后再去创建相应的通知渠道:
接下来修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            String channelId = "chat";
            String channelName = "聊天消息";
            int importance = NotificationManager.IMPORTANCE_HIGH;
            createNotificationChannel(channelId, channelName, importance);
            channelId = "subscribe";
            channelName = "订阅消息";
            importance = NotificationManager.IMPORTANCE_DEFAULT;
            createNotificationChannel(channelId, channelName, importance);
        }
    }
    
    @TargetApi(Build.VERSION_CODES.O)
    private void createNotificationChannel(String channelId, String channelName, int importance) {
        NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
        NotificationManager notificationManager = (NotificationManager) getSystemService(
                NOTIFICATION_SERVICE);
        notificationManager.createNotificationChannel(channel);
    }
    
}

这里我们在MainActivity中创建了两个通知渠道,首先要确保的是当前手机的系统版本必须是Android 8.0系统或者更高,因为低版本的手机系统并没有通知渠道这个功能,不做系统版本检查的话会在低版本手机上造成崩溃。

上面创建通知渠道的这部分代码,你可以写在MainActivity中,也可以写在Application中,实际上可以写在程序的任何位置,只需要保证在通知弹出之前调用就可以了。并且创建通知渠道的代码只在第一次执行的时候才会创建,以后每次执行创建代码系统会检测到该通知渠道已经存在了,因此不会重复创建,也并不会影响任何效率。

创建一个通知渠道的方式非常简单,这里我封装了一个createNotificationChannel()方法,里面的逻辑相信大家都看得懂。需要注意的是,创建一个通知渠道至少需要渠道ID、渠道名称以及重要等级这三个参数,其中渠道ID可以随便定义,只要保证全局唯一性就可以。渠道名称是给用户看的,需要能够表达清楚这个渠道的用途。重要等级的不同则会决定通知的不同行为,当然这里只是初始状态下的重要等级,用户可以随时手动更改某个渠道的重要等级,App是无法干预的。

上述代码我是模拟了这样一个场景。想象一下我们正在开发一个类似于微信的App,其中App通知主要可以分为两类,一类是我和别人的聊天消息,这类消息非常重要,因此重要等级我设为了IMPORTANCE_HIGH。另一类是公众号的订阅消息,这类消息不是那么重要,因此重要等级我设为了IMPORTANCE_DEFAULT。除此之外,重要等级还可以设置为IMPORTANCE_LOW、IMPORTANCE_MIN,分别对应了更低的通知重要程度。

现在就可以运行一下代码了,运行成功之后我们关闭App,进入到设置 -> 应用 -> 通知当中,查看NotificationTest这个App的通知界面,如下图所示:
在这里插入图片描述
刚才我们创建的两个通知渠道这里已经显示出来了。可以看到,由于这两个通知渠道的重要等级不同,通知的行为也是不同的,聊天消息可以发出提示音并在屏幕上弹出通知,而订阅消息只能发出提示音。

当然,用户还可以点击进去对该通知渠道进行任意的修改,比如降低聊天消息的重要等级,甚至是可以完全关闭该渠道的通知。

然后创建通知时指定它属于哪个渠道:
继续修改activity_main.xml中的代码,如下所示:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="发送聊天消息"
        android:onClick="sendChatMsg"
        />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="发送订阅消息"
        android:onClick="sendSubscribeMsg"
        />
</LinearLayout>

这里我们在布局文件中加入了两个按钮,很显然,一个是用于触发聊天消息渠道通知的,一个是用于触发订阅消息渠道通知的。

接下来在MainActivity中继续添加创建属于指定渠道通知的代码,如下所示:

public class MainActivity extends AppCompatActivity {

    ...
    
    public void sendChatMsg(View view) {
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        Notification notification = new NotificationCompat.Builder(this, "chat")
                .setContentTitle("收到一条聊天消息")
                .setContentText("今天中午吃什么?")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.drawable.icon)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon))
                .setAutoCancel(true)
                .build();
        manager.notify(1, notification);
    }
    
    public void sendSubscribeMsg(View view) {
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        Notification notification = new NotificationCompat.Builder(this, "subscribe")
                .setContentTitle("收到一条订阅消息")
                .setContentText("地铁沿线30万商铺抢购中!")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.drawable.icon)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon))
                .setAutoCancel(true)
                .build();
        manager.notify(2, notification);
    }
    
}

上面省略号表示的即是上面创建通知渠道号的代码,你也可以将这部分代码放到你自己的Application类中去指定。

这里我们分别在sendChatMsg()和sendSubscribeMsg()方法中触发了两条通知,创建通知的代码就不再多做解释了,和传统创建通知的方法没什么两样,只是在NotificationCompat.Builder中需要多传入一个通知渠道ID,那么这里我们分别传入了chat和subscribe这两个刚刚创建的渠道ID。

现在重新运行一下代码,并点击发送聊天消息按钮,效果如下图所示:
在这里插入图片描述
由于这是一条重要等级高的通知,因此会使用这种屏幕弹窗的方式来通知用户有消息到来。然后我们可以下拉展开通知栏,这里也能查看到通知的详细信息:
在这里插入图片描述
用户可以通过快速向左或者向右滑动来关闭这条通知。

接下来点击发送订阅消息按钮,你会发现现在屏幕上不会弹出一条通知提醒了,只会在状态栏上显示一个小小的通知图标:
在这里插入图片描述
因为订阅消息通知的重要等级是默认级别,这就是默认级别通知的展示形式。当然我们还是可以下拉展开通知栏,查看通知的详细信息:
在这里插入图片描述
不过上面演示的都是通知栏的传统功能,接下来我们看一看Android 8.0系统中通知栏特有的功能。

刚才提到了,快速向左或者向右滑动可以关闭一条通知,但如果你缓慢地向左或者向右滑动,就会看到这样两个按钮:
在这里插入图片描述
其中,左边那个时钟图标的按钮可以让通知延迟显示。比方说这是一条比较重要的通知,但是我暂时没时间看,也不想让它一直显示在状态栏里打扰我,我就可以让它延迟一段后时间再显示,这样我就暂时能够先将精力放在专注的事情上,等过会有时间了这条通知会再次显示出来,我不会错过任何信息。如下所示:
在这里插入图片描述
而右边那个设置图标的按钮就可以用来对通知渠道进行屏蔽和配置了,用户对每一个App的每一个通知渠道都有绝对的控制权,可以根据自身的喜好来进行配置和修改。如下所示:
在这里插入图片描述
比如说我觉得订阅消息老是向我推荐广告,实在是太烦了,我就可以将订阅消息的通知渠道关闭掉。这样我以后就不会再收到这个通知渠道下的任何消息,而聊天消息却不会受到影响,这就是8.0系统通知渠道最大的特色。

另外,点击上图中的所有类别就可以进入到当前应用程序通知的完整设置界面。

开发者如何管理通知渠道?
在前面的内容中我们已经了解到,通知渠道一旦创建之后就不能再通过代码修改了。既然不能修改的话那还怎么管理呢?为此,Android赋予了开发者读取通知渠道配置的权限,如果我们的某个功能是必须按照指定要求来配置通知渠道才能使用的,那么就可以提示用户去手动更改通知渠道配置。

比如我们开发的是一个类似于微信的App,聊天消息是至关重要的,如果用户不小心将聊天消息的通知渠道给关闭了,那岂不是所有重要的信息全部都丢了?为此我们一定要保证用户打开了聊天消息的通知渠道才行。

我们继续修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

    ...
    
    public void sendChatMsg(View view) {
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = manager.getNotificationChannel("chat");
            if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
                Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
                intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
                intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel.getId());
                startActivity(intent);
                Toast.makeText(this, "请手动将通知打开", Toast.LENGTH_SHORT).show();
            }
        }
        Notification notification = new NotificationCompat.Builder(this, "chat")
                ...
                .build();
        manager.notify(1, notification);
    }
    
    ...
}

这里我们对sendChatMsg()方法进行了修改,通过getNotificationChannel()方法获取到了NotificationChannel对象,然后就可以读取该通知渠道下的所有配置了。这里我们判断如果通知渠道的importance等于IMPORTANCE_NONE,就说明用户将该渠道的通知给关闭了,这时会跳转到通知的设置界面提醒用户手动打开。

现在重新运行一下程序,效果如下图所示:
在这里插入图片描述
可以看到,当我们将聊天消息的通知渠道关闭后,下次再次发送聊天消息将会直接跳转到通知设置界面,提醒用户手动将通知打开。

除了以上管理通知渠道的方式之外,Android 8.0还赋予了我们删除通知渠道的功能,只需使用如下代码即可删除:

NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.deleteNotificationChannel(channelId);

但是这个功能非常不建议大家使用。因为Google为了防止应用程序随意地创建垃圾通知渠道,会在通知设置界面显示所有被删除的通知渠道数量,如下图所示:
在这里插入图片描述
这样是非常不美观的,所以对于开发者来说最好的做法就是仔细规划好通知渠道,而不要轻易地使用删除功能。

Android 角标实现标准API
接下来我们就来学习一下如何在Android系统上实现未读角标的效果。
继续修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

    ...
    
    @TargetApi(Build.VERSION_CODES.O)
    private void createNotificationChannel(String channelId, String channelName, int importance) {
        NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
        channel.setShowBadge(true);
        NotificationManager notificationManager = (NotificationManager) getSystemService(
                NOTIFICATION_SERVICE);
        notificationManager.createNotificationChannel(channel);
    }
    
    public void sendSubscribeMsg(View view) {
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        Notification notification = new NotificationCompat.Builder(this, "subscribe")
                ...
                .setNumber(2)
                .build();
        manager.notify(2, notification);
    }
    
}

可以看到,这里我们主要修改了两个地方。第一是在创建通知渠道的时候,调用了NotificationChannel的setShowBadge(true)方法,表示允许这个渠道下的通知显示角标。第二是在创建通知的时候,调用了setNumber()方法,并传入未读消息的数量。

现在重新运行一下程序,并点击发送订阅消息按钮,然后在Launcher中找到NotificationTest这个应用程序,如下图所示:
在这里插入图片描述
可以看到,在图标的右上角有个绿色的角标,说明我们编写的角标功能已经生效了。

需要注意的是,即使我们不调用setShowBadge(true)方法,Android系统默认也是会显示角标的,但是如果你想禁用角标功能,那么记得一定要调用setShowBadge(false)方法。

但是未读数量怎么没有显示出来呢?这个功能还需要我们对着图标进行长按才行,效果如下图所示:
在这里插入图片描述
这样就能看到通知的未读数量是2了。

可能有些朋友习惯了iOS上的那种未读角标,觉得Android上这种还要长按的方式很麻烦。这个没有办法,因为这毕竟是Android原生系统,Google没有办法像国内手机厂商那样可以肆无忌惮地模仿iOS,要不然可能会吃官司的。但是我相信国内手机厂商肯定会将这部分功能进行定制,风格应该会类似于iOS。不过这都不重要,对于我们开发者来说,最好的福音就是有了统一的API标准,不管国内手机厂商以后怎么定制ROM,都会按照这个API的标准来定制,我们只需要使用这个API来进行编程就可以了。

本文转载自:https://blog.csdn.net/guolin_blog/article/details/79854070

猜你喜欢

转载自blog.csdn.net/gpf1320253667/article/details/85014198