Android笔记(五)
1. 通知
1.1 通知渠道1.2 基本用法1.3 进阶技巧 2. 调用摄像头和相册
2.1 调用摄像头拍照2.2 从相册中选择图片 3. 播放多媒体文件
3.1 音频3.2 视频
1. 通知 1.1 通知渠道为了避免通知被滥用,Android 8.0引入了通知渠道概念
通知渠道: 每条通知都要属于一个对应的渠道,每个应用程序都可以自由地创建当前应用拥有哪些通知渠道,但通知渠道的控制权在用户手上,可以自由选择这个通知渠道的重要程度,是否响铃、是否震动或者是否关闭这个渠道的通知
创建通知渠道:
需要一个NotificationManager对通知进行管理
使用NotificationChannel类创建一个通知渠道,并调用NotificationManager.createNotificationChannel()方法创建
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.o) {
// NotificationChannel和createNotificationChannel()都是Android 8.0新增API,所以使用时需要进行版本判断
val channel = NotificationChannel(channelId, channelName, importance)
manager.createNotificationChannel(channel)
}
1.2 基本用法
可以在Activity(应用场景较少)、BroadcastReceiver、Service中创建通知
- 使用一个Builder构造器来创建Notification对象
避免Android版本兼容问题,使用AndroidX库中提供的兼容API。提供了一个NotificationCompat类,使用这个类的构造器来构造Notification对象 调用NotificationManager的notify()方法让通知显示
第一个参数:指定的通知id第二个参数:Notification对象
val notification = NotificationCompat.Builder(context, channelId)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.large_icon))
.build()
manager.notify(1, notification)
PendingIntent:
和Intent一样可以用于启动Activity、Service以及发送广播等,不同在于Intent倾向于立即执行某个动作,PerndingIntent倾向于在某个合适的时机执行某个动作(可以理解为延迟执行的Intent)
根据需求使用getActivity()、getBroadcast()、getSerice()获取PendingIntent实例
第一个参数:Context第二个参数:一般不用,传入0即可第三个参数:Intent对象第四个参数:确定PendingIntent行为,通常传入0即可
FLAG_ONE_SHOTFLAG_NO_CREATEFLAG_CANCEL_CURRENTFLAG_UPDATe_CURRENT
NotificationCompat.Builder有一个setContentIntent(PendingIntent)方法,当用户点击通知时会执行相应的逻辑
通知取消:
如果没有在代码中对通知进行取消,就会一致显示在系统的状态栏上
在NotificationCompat.Builder中连缀一个setAutoCancel(),当点击通知时,通知会自动取消
val notification = NotificationCompat.Builder(context, channelId) ... .setAutoCancel(true) //cancel:取消 .build()
显示调用NotificationManager的cancel()
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager manager.cancel(1) // 传入的是指定的通知id1.3 进阶技巧
NotificationCompat.Builder中的API:
setStyle():构建富文本通知内容,接收一个NotificationCompat.Style参数,这个参数用于构建具体的富文本信息,如长文字、图片等
... .setStyle(NotificationCompat.BigTextStyle().bigText(这里是长文字,我也不知到要敲什么,只要够长的就行了,随便写些什么吧,凑字数凑字数凑字数凑字数凑字数凑字数凑字数凑字数凑字数)) .build()
重要等级越高越容易获得用户注意(弹出横幅、发出声音等),开发者只能在创建通知渠道时为其指定初始等级,用户可以随时进行修改,通知渠道一旦创建不能再通过代码修改
相关知识:
Notification.Builder API参考文档
2. 调用摄像头和相册 2.1 调用摄像头拍照class MainActivity : AppCompatActivity() {
val takePhoto = 1
lateinit var imageUri: Uri
lateinit var outputImage: File
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Btn.setOnClickListener {
outputImage = File(externalCacheDir, "output_image.jpg")
if (outputImage.exists()) {
outputImage.delete()
}
outputImage.createNewFile()
// 判断版本
imageUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
{
FileProvider.getUriForFile(this, "com.example.myapplication.filepovider", outputImage)
} else {
// 低于Android 7.0调用Uri的fromFile()将File对象转换为Uri对象
Uri.fromFile(outputImage)
}
// 启动相机程序
val intent = Intent("android.media.action.IMAGE_CAPTURE")
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
startActivityForResult(intent, takePhoto)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
takePhoto -> {
if (resultCode == Activity.RESULT_OK) {
// 调用BitmapFactory的decodeStream()将output_image.jpg图片解析成Bitmap对象
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri))
image.setImageBitmap(bitmap)
}
}
}
}
private fun rotateIfRequired(bitmap: Bitmap): Bitmap {
val exif = ExifInterface(outputImage.path)
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL)
return when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> rotateBitMap(bitmap, 90)
ExifInterface.ORIENTATION_ROTATE_180 -> rotateBitMap(bitmap, 180)
ExifInterface.ORIENTATION_ROTATE_270 -> rotateBitMap(bitmap, 270)
else -> bitmap
}
}
private fun rotateBitMap(bitmap: Bitmap, degree: Int): Bitmap {
val matrix = Matrix()
matrix.postRotate(degree.toFloat())
val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height,
matrix, true)
bitmap.recycle() // 将不需要的Bitmap对象回收
return rotatedBitmap
}
}
注:
为什么使用应用关联缓存目录存放图片
Android 6.0 开始读写SD卡为危险权限,存放在SD卡需要进行运行时权限处理,使用应用关联缓存目录则可以跳过这一步从Android 10.0开始,公有SD卡目录不在允许被应用程序直接访问,而是要使用作用域存储 为什么要进行版本判断
Android 7.0开始直接使用本地真实路径的Uri被认为不安全,会抛出FileUriExposedExceptionFileProvider是一种特殊的ContentProvider,使用类似ContentProvider机制来对数据进行保护,可选择性地将封装过的Uri共享给外部
2.2 从相册中选择图片...
class MainActivity : AppCompatActivity() {
...
val fromAlbum = 2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
fromAlbumBtn.setOnClickListener {
// 打开文件选择器
val intent = Intent(Intent.ACTION_OPEN_document)
intent.addCategory("Intent.CATEGORY_OPENABLE")
// 指定值显示图片
intent.type = "image/*"
startActivityForResult(intent, fromAlbum)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
...
fromAlbum -> {
if (resultCode == Activity.RESULT_OK && data != null) {
data.data?.let { uri ->
// 将选择的图片显示
val bitmap = getBitmapFromUri(uri)
image.setImageBitmap(bitmap)
}
}
}
}
}
private fun getBitmapFromUri(uri: Uri) = contentResolver
.openFileDescriptor(uri, "r")?.use {
BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
}
...
相关知识:
Android 10适配要点,作用域存储 - 掘金
3. 播放多媒体文件 3.1 音频一般使用MediaPlayer类实现
| 方法 | 描述 |
|---|---|
| setDataSource() | 设置播放音频文件的位置 |
| prepare() | 开始播放之前调用,已完成准备工作 |
| start() | 开始或继续播放音频 |
| pause() | 暂停播放音频 |
| reset() | 将MediaPlayer对象重置到刚刚创建的状态 |
| seekTo() | 从指定位置开始播放音频 |
| stop() | 停止播放音频,调用后MediaPlayer对象无法再播放音频 |
| release() | 释放有MediaPlayer对象相关的资源 |
| isPlaying() | 判断当前MediaPlayer是否正在播放音频 |
| getDuration() | 获取载入的音频文件时长 |
基本流程:
- 创建MediaPlayer对象调用setDataSource()方法设置音频文件路径调用prepare()方法使对象进入准备状态调用start()播放音频调用pause()暂停音频调用reset()停止播放
Android允许在项目中创建assets目录,这个目录下可以存放任意文件和子目录,这些文件会在项目打包时一并被打包到安装包中,在程序中国可以借助AssetManager类提供的接口对assets目录下文件进行读取
class MainActivity : AppCompatActivity() {
private val mediaPlayer = MediaPlayer()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initMediaPlayer()
play.setOnClickListener {
if (!mediaPlayer.isPlaying) {
mediaPlayer.start()
}
}
pause.setOnClickListener {
if (mediaPlayer.isPlaying) {
mediaPlayer.pause()
}
}
stop.setOnClickListener {
if (mediaPlayer.isPlaying) {
mediaPlayer.reset()
initMediaPlayer()
}
}
}
private fun initMediaPlayer() {
val assetManager = assets
val fd = assetManager.openFd("music.mp3")
mediaPlayer.setDataSource(fd.fileDescriptor, fd.startOffset, fd.length)
mediaPlayer.prepare()
}
override fun onDestroy() {
super.onDestroy()
mediaPlayer.stop()
mediaPlayer.release()
}
}
3.2 视频
主要使用VideoView控件类实现,将视频显示和控制集于一身
| 方法 | 描述 |
|---|---|
| seVideoPath() | 设置播放视频文件的位置 |
| start() | 开始或继续播放视频 |
| pause() | 暂停播放音频 |
| resume() | 将视频从头开始播放 |
| seekTo() | 从指定位置开始播放视频 |
| isPlaying() | 判断是否正在播放视频 |
| getDuration() | 获取载入的视频文件时长 |
VideoView不支持直接俄播放assets目录下的视频资源,所以创建res/raw目录,像音频、视频之类的资源文件可以放在里面
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val uri = Uri.parse("android.resource://$packageName/${R.raw.video}")
videoView.setVideoURI(uri)
play.setOnClickListener {
if (!videoView.isPlaying) {
videoView.start()
}
}
pause.setOnClickListener {
if (videoView.isPlaying) {
videoView.pause()
}
}
replay.setOnClickListener {
if (videoView.isPlaying) {
videoView.resume()
}
}
}
override fun onDestroy() {
super.onDestroy()
videoView.suspend() // 释放资源
}
}
}
}
pause.setOnClickListener {
if (videoView.isPlaying) {
videoView.pause()
}
}
replay.setOnClickListener {
if (videoView.isPlaying) {
videoView.resume()
}
}
}
override fun onDestroy() {
super.onDestroy()
videoView.suspend() // 释放资源
}
}



