手机多媒体

Android中一些常用的多媒体功能的使用技巧。

通知

应用程序如果想要发出通知,也必须创建自己的通知渠道才行

创建

  • 创建通知渠道

(1)首先需要一个NotificationManager对通知进行管理

(2)接下来要使用NotificationChannel类构建一个通知渠道,并调用NotificationManagercreateNotificationChannel()完成创建。

1
2
3
4
5
6
7
8
9
10
//参数用于确定获取系统的哪个服务
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

//Build.VERSION.SDK_INT 是一个整数,代表当前设备运行的 Android 系统的 API 级别(也称为版本号)。
//Build.VERSION_CODES.O 是 Android Oreo 版本的 API 级别的一个常量值。在 Android SDK 中,每个 Android 版本都有对应的 API 级别常量,它们都位于 Build.VERSION_CODES 类中。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//渠道ID:保证唯一就可以,渠道名称:给用户看的,渠道等级:IMPORTANCE_HIGH、IMPORTANCE_DEFAULT、IMPORTANCE_LOW、IMPORTANCE_MIN
val channel = NotificationChannel(channelId, channelName, importance)
manager.createNotificationChannel(channel)
}
  • 创建通知
1
2
3
4
5
6
7
8
9
//参数中的channelId与上面的渠道id相同
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)//让通知显示出来,1:id,保证每个通知指定的id不同
  • 举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("normal", "Normal",NotificationManager.
IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
sendNotice.setOnClickListener {
val notification = NotificationCompat.Builder(this, "normal")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,
R.drawable.large_icon))
.build()
manager.notify(1, notification)
}
}

}

手机的应用通知中可以看到:

image-20240316234300650

接下来,点击按钮,,可以看到状态栏有小图标,下拉可以看到通知详情

image-20240316234323372

但目前点击通知内容没有反映,因为我们还没有实现这部分的功能,下面进行实现,

使用Pendntenting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
...
sendNotice.setOnClickListener {
val intent = Intent(this, NotificationActivity::class.java)
val pi = PendingIntent.getActivity(this, 0, intent, 0)
val notification = NotificationCompat.Builder(this, "normal")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,
R.drawable.large_icon))
.setContentIntent(pi)
.build()
manager.notify(1, notification)
}
}

}

但点击之后图标还没有消失,下面在代码中实现,点击通知后图标消失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//方法一
val notification = NotificationCompat.Builder(this, "normal")
...
.setAutoCancel(true)
.build()

//方法二
class NotificationActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_notification)
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
manager.cancel(1)//当时我们给这条通知设置的id就是1
}

}

通知的进阶技巧

(1)setStyle()方法,这个方法允许我们构建出富文本的通知内容

当通知的内容很多,通知内容是无法完整显示的,多余的部分会用省略号代替。但是如果你真的非常需要在通知当中显示一段长文字,Android也是支持的,通过setStyle()方法就可以做到

1
2
3
4
5
6
val notification = NotificationCompat.Builder(this, "normal")
...
.setStyle(NotificationCompat.BigTextStyle().bigText("Learn how to build
notifications, send and sync data, and use voice actions. Get the official
Android IDE and developer tools to build apps for Android."))
.build()

使用了setStyle()方法替代setContentText()方法

除了显示长文字之外,通知里还可以显示一张大图片

1
2
3
4
5
val notification = NotificationCompat.Builder(this, "normal")
...
.setStyle(NotificationCompat.BigPictureStyle().bigPicture(
BitmapFactory.decodeResource(resources, R.drawable.big_image)))
.build()

通过BitmapFactorydecodeResource()方法将图片解析成Bitmap对象

(2)通知渠道的重要等级

就是通知渠道的重要等级越高,发出的通知就越容易获得用户的注意。比如高重要等级的通知渠道发出的通知可以弹出横幅、发出声音,而低重要等级的通知渠道发出的通知不仅可能会在某些情况下被隐藏,而且可能会被改变显示的顺序,将其排在更重要的通知之后。

由于通知渠道一旦创建就不能再通过代码修改了。既然无法修改之前创建的通知渠道,那么我们就只好再创建一个新的通知渠道来测试了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
...
val channel2 = NotificationChannel("important", "Important",
NotificationManager.IMPORTANCE_HIGH)
manager.createNotificationChannel(channel2)
}
sendNotice.setOnClickListener {
val intent = Intent(this, NotificationActivity::class.java)
val pi = PendingIntent.getActivity(this, 0, intent, 0)
val notification = NotificationCompat.Builder(this, "important")
...
}
}

}

image-20240316234510616

调用摄像头和相册

我们平时在使用QQ或微信的时候经常要和别人分享图片,这些图片可以是用手机摄像头拍的,也可以是从相册中选取的。这样的功能实在是太常见了,几乎是应用程序必备的功能,那么本节我们就学习一下调用摄像头和相册方面的知识。

(1)调用摄像头

举例:点击按钮,启动照相机,拍照,将拍下来的图片展示到按钮的下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
 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)
takePhotoBtn.setOnClickListener {
// 创建File对象,用于存储拍照后的图片
outputImage = File(externalCacheDir, "output_image.jpg")
if (outputImage.exists()) {
outputImage.delete()
}
outputImage.createNewFile()
//判断设备的系统是否大于Android7.0
imageUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(this, "com.example.cameraalbumtest.
fileprovider", outputImage)
} else {
Uri.fromFile(outputImage) //将file对象转为uri对象
}
// 启动相机程序
val intent = Intent("android.media.action.IMAGE_CAPTURE")
//当使用相机拍摄照片或录制视频时,可以通过设置 MediaStore.EXTRA_OUTPUT 来指定保存文件的路径和名称。
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) {
// 用于读取由 imageUri 指定的文件的内容
//BitmapFactory 是一个用于创建位图(Bitmap)对象的工厂类。decodeStream()会从给定的输入流中解码位图数据,并返回一个位图对象。
val bitmap = BitmapFactory.decodeStream(contentResolver.
openInputStream(imageUri))
imageView.setImageBitmap(rotateIfRequired(bitmap))
}
}
}
}
//手机认为打开摄像头进行拍摄时手机就应该是横屏的,因此回到竖屏的情况下就会发生90度的旋转。
//为此,这里我们又加上了判断图片方向的代码,如果发现图片需要进行旋转,
//那么就先将图片旋转相应的角度,然后再显示到界面上。
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
}
}

具体来说,FileProvider.getUriForFile() 方法的参数包括:

  1. context:当前上下文,通常是 Activity 或 Service 的实例。
  2. authority:FileProvider 的授权信息,即在 AndroidManifest.xml 文件中注册的 FileProvider 的 authorities 属性的值。
  3. file:要获取内容 URI 的文件对象。

调用 FileProvider.getUriForFile() 方法后,它会返回一个内容 URI,这个 URI 可以被其他应用程序访问,用于获取你的应用程序中指定文件的内容。

不过现在还没结束,刚才提到了ContentProvider,那么我们自然要在AndroidManifest.xml中对它进行注册才行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cameraalbumtest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.cameraalbumtest.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

这里还在<provider>标签的内部使用<meta-data>指定Uri的共享路径,并引用了一个@xml/file_paths资源。

右击res目录→New→Directory,创建一个xml目录,接着右击xml目录→New→File,创建一个file_paths.xml文件。然后修改file_paths.xml文件中的内容

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="/" />
</paths>

external-path就是用来指定Uri共享路径的,name属性的值可以随便填,path属性的值表示共享的具体路径,这里使用一个单斜线表示将整个SD卡进行共享,当然你也可以仅共享存放output_image.jpg这张图片的路径。

(2)从相册中选择图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class MainActivity : AppCompatActivity() {
...
val fromAlbum = 2

override fun onCreate(savedInstanceState: Bundle?) {
...
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.getData():获取选中图片的uri
data.data?.let { uri ->
// 将Uri转换成Bitmap对象
val bitmap = getBitmapFromUri(uri)
// 将选择的图片显示
imageView.setImageBitmap(bitmap)
}
}
}
}
}

private fun getBitmapFromUri(uri: Uri) = contentResolver
.openFileDescriptor(uri, "r")?.use {
BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
}
...
}

播放多媒体文件

(1)播放音频

在Android中播放音频文件一般是使用MediaPlayer类实现的,它对多种格式的音频文件提供了非常全面的控制方法,从而使播放音乐的工作变得十分简单。

首先来创建assets目录吧,它必须创建在app/src/main这个目录下面,也就是和java、res这两个目录是平级的。右击app/src/main→New→Directory,在弹出的对话框中输入“assets”,目录就创建完成了。将音频放入到此目录中

image-20240316234600119

使用流程:首先需要创建一个MediaPlayer对象,然后调用setDataSource()方法设置音频文件的路径,再调用prepare()方法使MediaPlayer进入准备状态,接下来调用start()方法就可以开始播放音频,调用pause()方法就会暂停播放,调用reset()方法就会停止播放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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()
}

}

在initMediaPlayer()方法中,首先通过getAssets()方法得到了一个AssetManager的实例,AssetManager可用于读取assets目录下的任何资源。

调用reset()方法将MediaPlayer重置为刚刚创建的状态,然后重新调用一遍initMediaPlayer()方法

(2)播放视频

主要是使用VideoView类来实现的。这个类将视频的显示和控制集于一身,我们仅仅借助它就可以完成一个简易的视频播放器。

img

VideoView不支持直接播放assets目录下的视频资源,而是使用res目录下允许我们再创建一个raw目录,像诸如音频、视频之类的资源文件也可以放在这里,并且VideoView是可以直接播放这个目录下的视频资源的。将视频资源放入这个文件夹中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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()
}

}

首先在onCreate()方法中调用了Uri.parse()方法,将raw目录下的video.mp4文件解析成了一个Uri对象,这里使用的写法是Android要求的固定写法。然后调用VideoView的setVideoURI()方法将刚才解析出来的Uri对象传入,这样VideoView就初始化完成了

注意:VideoView并不是一个万能的视频播放工具类,它在视频格式的支持以及播放效率方面都存在着较大的不足。所以,如果想要仅仅使用VideoView就编写出一个功能非常强大的视频播放器是不太现实的。


手机多媒体
http://example.com/2024/03/16/手机多媒体/
作者
zlw
发布于
2024年3月16日
许可协议