使用通知
通知(notification)是Android系统中比较有特色的一个功能,当某个应用程序希望向用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知来实现。发出一条通知后,手机最上方的状态栏中会显示一个通知的图标,下拉状态栏后可以看到通知的详细内容。
创建通知渠道
什么是通知渠道呢?顾名思义,就是每条通知都要属于一个对应的渠道。每个应用程序都可以自由地创建当前应用拥有哪些通知渠道,但是这些通知渠道的控制权是掌握在用户手上的。用户可以自由地选择这些通知渠道的重要程度,是否响铃、是否振动或者是否要关闭这个渠道的通知。拥有了这些控制权之后,用户就再也不用害怕那些垃圾通知的打扰了,因为用户可以自主地选择关心哪些通知、不关心哪些通知。以刚才的场景举例,微博就可以创建两种通知渠道,一个关注,一个推荐。而我作为用户,如果对推荐类的通知不感兴趣,那么我就可以直接将推荐通知渠道关闭,这样既不影响我接收关心的通知,又不会让那些我不关心的通知来打扰我了。对于每个应用来说,通知渠道的划分是非常考究的,因为通知渠道一旦创建之后就不能再修改了,因此开发者需要仔细分析自己的应用程序一共有哪些类型的通知,然后再去创建相应的通知渠道。
使用方法
1.NotificationManager对通知进行管理
首先需要一个NotificationManager对通知进行管理,可以通过调用Context的getSystemService()方法获取。getSystemService()方法接收一个字符串参数用于确定获取系统的哪个服务,这里我们传入Context.NOTIFICATION_SERVICE即可。因此,获取NotificationManager的实例就可以写成:
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
接下来要使用NotificationChannel类构建一个通知渠道,并调用NotificationManager的createNotificationChannel()方法完成创建。由于NotificationChannel类和createNotificationChannel()方法都是Android 8.0系统中新增的API,因此我们在使用的时候还需要进行版本判断才可以,写法如下:
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {// 获取NotificationManager服务android.app.NotificationManager manager = (android.app.NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);// 定义通知渠道的ID、名称和重要性String channelId = "your_channel_id"; // 这里替换成您的渠道IDString channelName = "Your Channel Name"; // 这里替换成您的渠道名称int importance = android.app.NotificationManager.IMPORTANCE_DEFAULT; // 设置通知的重要性等级// 创建NotificationChannel对象android.app.NotificationChannel channel = new android.app.NotificationChannel(channelId, channelName, importance);// 配置NotificationChannel的属性(可选)channel.setDescription("Your channel description"); // 设置渠道描述// 注册通知渠道manager.createNotificationChannel(channel);
}
创建一个通知渠道至少需要渠道ID、渠道名称以及重要等级这3个参数,其中渠道ID可以随便定义,只要保证全局唯一性就可以。渠道名称是给用户看的,需要可以清楚地表达这个渠道的用途。通知的重要等级主要有IMPORTANCE_HIGH、IMPORTANCE_DEFAULT、IMPORTANCE_LOW、IMPORTANCE_MIN这几种,对应的重要程度依次从高到低。不同的重要等级会决定通知的不同行为,后面我们会通过具体的例子进行演示。当然这里只是初始状态下的重要等级,用户可以随时手动更改某个通知渠道的重要等级,开发者是无法干预的。
通知的基本用法
了解了如何创建通知渠道之后,下面我们就来看一下通知的使用方法吧。通知的用法还是比较灵活的,既可以在Activity里创建,也可以在BroadcastReceiver里创建,当然还可以在后面我们即将学习的Service里创建。相比于BroadcastReceiver和Service,在Activity里创建通知的场景还是比较少的,因为一般只有当程序进入后台的时候才需要使用通知。
2.使用一个Builder构造器来创建Notification对象(NotificationCompat)
首先需要使用一个Builder构造器来创建Notification对象,但问题在于,Android系统的每一个版本都会对通知功能进行或多或少的修改,API不稳定的问题在通知上凸显得尤其严重,比方说刚刚介绍的通知渠道功能在Android 8.0系统之前就是没有的。那么该如何解决这个问题呢?其实解决方案我们之前已经见过好几回了,就是使用AndroidX库中提供的兼容API。AndroidX库中提供了一个NotificationCompat类,使用这个类的构造器创建Notification对象,就可以保证我们的程序在所有Android系统版本上都能正常工作了,代码如下所示:
// 使用NotificationChannel的ID创建NotificationCompat.Buildernotification = new NotificationCompat.Builder(context, channelId).setSmallIcon(R.drawable.ic_notification) // 设置小图标.setContentTitle("Your Title") // 设置通知标题.setContentText("Your Content") // 设置通知内容.setChannelId(channelId) // 设置通知渠道ID.build();
NotificationCompat.Builder的构造函数中接收两个参数:
第一个参数是context,这个没什么好说的;
第二个参数是渠道ID,需要和我们在创建通知渠道时指定的渠道ID相匹配才行。
当然,上述代码只是创建了一个空的Notification对象,并没有什么实际作用,我们可以在最终的build()方法之前连缀任意多的设置方法来创建一个丰富的Notification对象,
- setContentTitle()方法用于指定通知的标题内容,下拉系统状态栏就可以看到这部分内容。
- setContentText()方法用于指定通知的正文内容,同样下拉系统状态栏就可以看到这部分内容。
- setSmallIcon()方法用于设置通知的小图标,注意,只能使用纯alpha图层的图片进行设置,小图标会显示在系统状态栏上。
- setLargeIcon()方法用于设置通知的大图标,当下拉系统状态栏时,就可以看到设置的大图标了。
3.NotificationManager的notify()方法
以上工作都完成之后,只需要调用NotificationManager的notify()方法就可以让通知显示出来了。
notify()方法接收两个参数:
第一个参数是id,要保证为每个通知指定的id都是不同的;
第二个参数则是Notification对象,这里直接将我们刚刚创建好的Notification对象传入即可。
因此,显示一个通知就可以写成:
manager.notify(1, notification)
新建一个NotificationTest项目,并修改activity_main.xml中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/sendNotice"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Send Notice" />
</LinearLayout>
布局文件非常简单,里面只有一个“Send Notice”按钮,用于发出一条通知。接下来修改MainActivity中的代码,如下所示:
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {private Button sendNotice;private NotificationManager manager;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 获取系统服务中的NotificationManagermanager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);// 检查Android版本是否在O(API级别26)及以上if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {// 创建一个新的通知渠道NotificationChannel channel = new NotificationChannel("normal", "Normal", NotificationManager.IMPORTANCE_DEFAULT);// 注册通知渠道到系统中manager.createNotificationChannel(channel);}sendNotice = findViewById(R.id.sendNotice);sendNotice.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Notification.Builder builder = new Notification.Builder(MainActivity.this, "normal").setContentTitle("This is content title").setContentText("This is content text").setSmallIcon(R.drawable.ic_launcher_background).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground));Notification notification = builder.build();manager.notify(1, notification);}});}
}
应用程序尝试在目标SDK为Android 13(API级别33)或更高版本的设备上发送通知,但没有正确请求或检查POST_NOTIFICATIONS权限。从Android 13开始,应用必须在运行时动态请求此权限才能发送通知。
为了修复这个问题,需要确保应用在尝试发送通知之前已经获得了POST_NOTIFICATIONS权限。这通常涉及到两步:
- 在应用的AndroidManifest.xml文件中声明
POST_NOTIFICATIONS
权限。 - 在应用的代码中动态请求这个权限。
我们首先获取了NotificationManager的实例,并创建了一个ID为normal通知渠道。创建通知渠道的代码只在第一次执行的时候才会创建,当下次再执行创建代码时,系统会检测到该通知渠道已经存在了,因此不会重复创建,也并不会影响运行效率。
接下来在“Send Notice”按钮的点击事件里完成了通知的创建工作,创建的过程正如前面所描述的一样。
注意,在NotificationCompat.Builder的构造函数中传入的渠道ID也必须叫normal,如果传入了一个不存在的渠道ID,通知是无法显示出来的
这个时候就可以发送通知了
PendingIntent
如果你使用过Android手机,此时应该会下意识地认为这条通知是可以点击的。但是当你去点击它的时候,会发现没有任何效果。不对啊,每条通知被点击之后都应该有所反应呀。其实要想实现通知的点击效果,我们还需要在代码中进行相应的设置,这就涉及了一个新的概念——PendingIntent。
PendingIntent从名字上看起来就和Intent有些类似,它们确实存在不少共同点。比如它们都可以指明某一个“意图”,都可以用于启动Activity、启动Service以及发送广播等。不同的是,Intent倾向于立即执行某个动作,而PendingIntent倾向于在某个合适的时机执行某个动作。所以,也可以把PendingIntent简单地理解为延迟执行的Intent。
PendingIntent的用法同样很简单,它主要提供了几个静态方法用于获取PendingIntent的实例,可以根据需求来选择是使用getActivity()方法、getBroadcast()方法,还是getService()方法。
这几个方法所接收的参数都是相同的:
第一个参数依旧是Context,不用多做解释;
第二个参数一般用不到,传入0即可;
第三个参数是一个Intent对象,我们可以通过这个对象构建出PendingIntent的“意图”;
第四个参数用于确定PendingIntent的行为,有FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT这4种值可选,每种值的具体含义你可以查看文档,通常情况下这个参数传入0就可以了
对PendingIntent有了一定的了解后,我们再回过头来看一下NotificationCompat.Builder。这个构造器还可以连缀一个setContentIntent()方法,接收的参数正是一个PendingIntent对象。因此,这里就可以通过PendingIntent构建一个延迟执行的“意图”,当用户点击这条通知时就会执行相应的逻辑。
现在我们来优化一下NotificationTest项目,给刚才的通知加上点击功能,让用户点击它的时候可以启动另一个Activity
首先需要准备好另一个Activity,右击com.example.notificationtest包→New→Activity→Empty Activity,新建NotificationActivity。然后修改activity_notification.xml中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent" ><TextView android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:textSize="24sp"android:text="This is notification layout" />
</RelativeLayout>
这样就把NotificationActivity准备好了,下面我们修改MainActivity中的代码,给通知加入点击功能,如下所示:
sendNotice.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent(MainActivity.this, noticebestart.class);PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0, intent, PendingIntent.FLAG_IMMUTABLE);NotificationCompat.Builder builder = new NotificationCompat.Builder(MainActivity.this, "normal").setContentTitle("This is content title").setContentText("This is content text").setSmallIcon(R.drawable.ic_launcher_background).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground)).setContentIntent(pi).setAutoCancel(true);Notification notification = builder.build();manager.notify(1, notification);}});
}
可以看到,这里先是使用Intent表达出我们想要启动NotificationActivity的“意图”,然后将构建好的Intent对象传入PendingIntent的getActivity()方法里,以得到PendingIntent的实例,接着在NotificationCompat.Builder中调用setContentIntent()方法,把它作为参数传入即可。现在重新运行一下程序,并点击“Send Notice”按钮,依旧会发出一条通知。然后下拉系统状态栏,点击一下该通知,就会打开NotificationActivity的界面了
setAutoCancel()方法传入true,就表示当点击这个通知的时候,通知会自动取消。
显式地调用NotificationManager的cancel()方法将它取消。这里我们在cancel()方法中传入了1,这个1是什么意思呢?还记得在创建通知的时候给每条通知指定的id吗?当时我们给这条通知设置的id就是1。因此,如果你想取消哪条通知,在cancel()方法中传入该通知的id就行了。
通知的进阶技巧
setStyle()方法
这个方法允许我们构建出富文本的通知内容。也就是说,通知中不光可以有文字和图标,还可以包含更多的东西。setStyle()方法接收一个NotificationCompat.Style参数,这个参数就是用来构建具体的富文本信息的,如长文字、图片等。在开始使用setStyle()方法之前,我们先来做一个试验吧,之前的通知内容都比较短,如果设置成很长的文字会是什么效果呢?(会发现我们的信息被省略掉了)
如果你真的非常需要在通知当中显示一段长文字通过setStyle()方法就可以做到,具体写法如下:
.setStyle(NotificationCompat.BigTextStyle().bigText("Learn how to buildnotifications, send and sync data, and use voice actions. Get the officialAndroid IDE and developer tools to build apps for Android.")).build()
这里使用了setStyle()方法替代setContentText()方法。在setStyle()方法中,我们创建了一个NotificationCompat.BigTextStyle对象,这个对象就是用于封装长文字信息的,只要调用它的bigText()方法并将文字内容传入就可以了。
除了显示长文字之外,通知里还可以显示一张大图片,具体用法是基本相似的:
.setStyle(NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(resources, R.drawable.big_image)))
可以看到,这里仍然是调用的setStyle()方法,这次我们在参数中创建了一个NotificationCompat.BigPictureStyle对象,这个对象就是用于设置大图片的,然后调用它的bigPicture()方法并将图片传入。这里我事先准备好了一张图片,通过BitmapFactory的decodeResource()方法将图片解析成Bitmap对象,再传入bigPicture()方法中就可以了。
重要等级
接下来,我们学习一下不同重要等级的通知渠道对通知的行为具体有什么影响。其实简单来讲,就是通知渠道的重要等级越高,发出的通知就越容易获得用户的注意。
比如高重要等级的通知渠道发出的通知可以弹出横幅、发出声音,而低重要等级的通知渠道发出的通知不仅可能会在某些情况下被隐藏,而且可能会被改变显示的顺序,将其排在更重要的通知之后。
但需要注意的是,开发者只能在创建通知渠道的时候为它指定初始的重要等级,如果用户不认可这个重要等级的话,可以随时进行修改,开发者对此无权再进行调整和变更,因为通知渠道一旦创建就不能再通过代码修改了。
既然无法修改之前创建的通知渠道,那么我们就只好再创建一个新的通知渠道来测试了。修改MainActivity中的代码,如下所示:
public class MainActivity extends AppCompatActivity {private Button sendNotice;private NotificationManager manager;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// setContentView(R.layout.your_layout); // 请替换为你的布局文件// 初始化按钮等组件sendNotice = findViewById(R.id.send_notice_button); // 请替换为实际的按钮IDmanager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);// 设置按钮的点击事件sendNotice.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 创建IntentIntent intent = new Intent(MainActivity.this, NotificationActivity.class);PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0, intent, 0);// 检查Android版本,如果是8.0以上,则创建通知渠道if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {NotificationChannel channel2 = new NotificationChannel("important", "Important", NotificationManager.IMPORTANCE_HIGH);manager.createNotificationChannel(channel2);}// 创建通知Notification notification = new NotificationCompat.Builder(MainActivity.this, "important").setContentTitle("Your Title") // 设置通知标题.setContentText("Your Text") // 设置通知内容.setSmallIcon(R.drawable.ic_notification) // 设置通知小图标.setContentIntent(pi) // 设置通知点击后的行为.build();// 发送通知manager.notify(1, notification); // 1是通知的ID,可以自定义}});}
}
可以看到,这次的通知不是在系统状态栏显示一个小图标了,而是弹出了一个横幅,并附带了通知的详细内容,表示这是一条非常重要的通知。不管用户现在是在玩游戏还是看电影,这条通知都会显示在最上方,以此引起用户的注意。当然,使用这类通知时一定要小心,确保你的通知内容的确是至关重要的,不然如果让用户产生排斥感的话,可能会造成适得其反的效果。
- MMKV简介: MMKV是一种专为Android设计的键值存储方案,支持多进程访问,能够提供比SharedPreferences更快的读写速度。
- 使用场景: MMKV适用于需要快速读写操作和跨进程数据共享的应用场景,例如用户偏好设置、应用配置等。
- 初始化方法: 文章介绍了如何初始化MMKV,包括创建实例和配置选项,以便开发者能够根据自己的需求进行设置。
- 数据读取原理: 通过视频教程,作者详细解释了MMKV的数据存储和读取机制,帮助开发者理解其工作原理。
- 与其他存储方案的比较: 文章还对比了MMKV与传统的SharedPreferences以及文件存储方案,突出了MMKV在性能和多进程支持方面的优势。
MMKV是一种专为Android设计的键值存储方案,支持多进程访问,能够提供比SharedPreferences更快的读写速度。
- 使用场景: MMKV适用于需要快速读写操作和跨进程数据共享的应用场景,例如用户偏好设置、应用配置等。
- 初始化方法: 文章介绍了如何初始化MMKV,包括创建实例和配置选项,以便开发者能够根据自己的需求进行设置。
- 数据读取原理: 通过视频教程,作者详细解释了MMKV的数据存储和读取机制,帮助开发者理解其工作原理。
- 与其他存储方案的比较: 文章还对比了MMKV与传统的SharedPreferences以及文件存储方案,突出了MMKV在性能和多进程支持方面的优势。