Android开发基础8-手机多媒体

本篇文章主要介绍以下几个知识点:

  • 通知的使用;
  • 调用系统相机拍照或调用相册选取照片;
  • 播放多媒体文件。

8.1 使用通知

  当某个应用程序希望向用户发送一些提示消息,而该应用程序又不在前台运行时,就可借助通知(Notification)来实现。

8.1.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
public class NotificationActivity extends AppCompatActivity implements View.OnClickListener {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notification);

Button btn_send_notice = (Button) findViewById(R.id.btn_send_notice);
btn_send_notice.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_send_notice:
// 1. 获取 NotificationManager 实例来对通知进行管理
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// 2. 使用 Builder 构造器来创建 Notification 对象
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("This is content title") // 标题内容
.setContentText("This is content text") // 正文内容
.setWhen(System.currentTimeMillis()) // 通知被创建的时间
.setSmallIcon(R.mipmap.ic_launcher) // 通知的小图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))// 通知的大图标
.build();
// 3. 显示通知. 其中notify()的两个参数:第一个是id,要保证为每个通知所指定的id都是不同的;
// 第二个参数是 Notification 对象
manager.notify(1,notification);
break;

default:
break;
}
}
}

  上述代码,界面上就一个 发送通知 按钮,在按钮的点击事件里面完成了通知的创建工作。运行程序,点击按钮效果如下:

  接下来实现通知的点击效果,这里要用到 PendingIntent ,即在某个合适的时机去执行某个动作。PendingIntent 实例可由 getActivity()、getBroadcast()、getServices() 等方法获取,其接收的参数:第一个是 Context;第二个一般用不到传0即可;第三个是 Intent 对象;第四个用于确定 PendingIntent 的行为,一般传0即可。

  首先,准备好一个点击通知跳转的活动 NotificationTestActivity ,然后,修改 NotificationActivity 的代码如下:

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
public class NotificationActivity extends AppCompatActivity implements View.OnClickListener {

. . .

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_send_notice:
Intent intent = new Intent(this,NotificationTestActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);

// 1. 获取 NotificationManager 实例来对通知进行管理
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// 2. 使用 Builder 构造器来创建 Notification 对象
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentIntent(pi) // 传入pi
.build();
// 3. 显示通知
manager.notify(1,notification);
break;

default:
break;
}
}
}

  现在重新运行程序并点击按钮,效果如下:

  细心的你会发现系统状态栏上的通知图标还没消失,解决方法有两种,一种是在 NotificationCompat.Builder 中再连缀一个 setAutoCancel() 方法,如下:

1
2
3
4
Notification notification = new NotificationCompat.Builder(this)
. . .
.setAutoCancel(true)
.build();

  另外一种是显式调用 NotificationManager 的 cancel()方法,在点击通知跳转的活动 NotificationTestActivity 中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
public class NotificationTestActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notification_test);

NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(1); // 传入创建通知时指定的id
}
}

8.1.2 通知的进阶用法

  上面提到了通知的基本用法,接下来介绍一些通知的其他技巧,比如:

  • 在通知发出时播放一段音频,调用 setSound() 方法:
1
2
3
4
Notification notification = new NotificationCompat.Builder(this)
. . .
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg"))) // 在音频目录下选个音频文件
.build();
  • 在通知到来时让手机振动,设置 vibrate 属性:
1
2
3
4
Notification notification = new NotificationCompat.Builder(this)
. . .
.setVibrate(new long[]{0,1000,1000,1000}) // 数组下标0表静止的时长,下标1表振动的时长,下标2表静止的时长,以此类推
.build();

  当然别忘了声明振动权限:

1
<uses-permission android:name="android.permission.VIBRATE" />
  • 在通知到来时显式手机 LED 灯,调用 setLights() 方法:
1
2
3
4
Notification notification = new NotificationCompat.Builder(this)
. . .
.setLights(Color.GREEN,1000,1000) // 三个参数:LED 灯的颜色、灯亮时长、灯暗时长
.build();
  • 当然也可直接使用默认效果,如下:
1
2
3
4
Notification notification = new NotificationCompat.Builder(this)
. . .
.setDefaults(NotificationCompat.DEFAULT_ALL)
.build();

8.1.3 通知的高级功能

  上面提到了通知的进阶用法,接下来介绍一些通知的高级功能,比如:

  • NotificationCompat.Builder 类中的 setStyle() 方法
      这个方法允许我们构建出富文本的通知内容,setStyle() 方法接收一个 NotificationCompat.style 参数,这个参数用来构造具体的富文本信息,如长文字、图片等。
      在通知当中显示一段长文字,代码如下:
1
2
3
4
5
6
7
Notification notification = new NotificationCompat.Builder(this)
. . .
// 在setStyle方法中创建NotificationCompat.BigTextStyle对象,调用其bigText()方法传入文字即可
.setStyle(new NotificationCompat.BigTextStyle().bigText("红红火火恍恍惚惚," +
"哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈呵呵,红红火火恍恍惚惚" +
"哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈"))
.build();

  效果如图所示:

  在通知当中显示一张图片,代码如下:

1
2
3
4
5
Notification notification = new NotificationCompat.Builder(this)
. . .
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.
decodeResource(getResources(),R.drawable.big_image)))))
.build();

  效果如图所示:

  • NotificationCompat.Builder 类中的 setPriority() 方法
      这个方法用于设置通知的重要程度,setPriority() 方法接收一个整型参数。
      将通知的重要程度设置最高,代码如下:
1
2
3
4
5
6
Notification notification = new NotificationCompat.Builder(this)
. .
// 一共有5个常量可选:PRIORITY_DEFAULT默认、PRIORITY_MIN最低、PRIORITY_LOW较低、
// PRIORITY_HIGH较高、PRIORITY_MAX最高
.setPriority(NotificationCompat.PRIORITY_MAX)
.build();

  效果如图所示:

8.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
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
public class TakePhotoActivity extends AppCompatActivity {

private Button btn_take_photo; // 调用摄像头拍照
private Button btn_select_photo; // 从相册中选照片
private ImageView iv_show_photo; // 展示照片

public static final int TAKE_PHOTO = 1;
public static final int CHOOSE_PHOTO = 2;
private Uri imageUri;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_take_photo);

btn_take_photo = (Button) findViewById(R.id.btn_take_photo);
btn_select_photo = (Button) findViewById(R.id.btn_select_photo);
iv_show_photo = (ImageView) findViewById(R.id.iv_show_photo);

// 调用相机拍照
btn_take_photo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 创建File对象,用于存储拍照后的图片
File outputImage = new File(getExternalCacheDir(),"output_image.jpg");
try{
if (outputImage.exists()){
outputImage.delete();
}
outputImage.createNewFile();
}catch (IOException e){
e.printStackTrace();
}
// 将File对象转换成Uri对象
if (Build.VERSION.SDK_INT >= 24){
// getUriForFile() 方法接收三个参数:Context对象、任意唯一的字符串、File对象
imageUri = FileProvider.getUriForFile(TakePhotoActivity.this, "com.wonderful." +
"myfirstcode.chapter8.TakePhotoActivity.fileprovider",outputImage);
}else {
imageUri = Uri.fromFile(outputImage);
}
// 启动相机程序
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
startActivityForResult(intent,TAKE_PHOTO);
}
});

// 打开相册选照片
btn_select_photo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ActivityCompat.checkSelfPermission(TakePhotoActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(TakePhotoActivity.this,
new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}else {
// 若已授权,则直接执行打开相册的逻辑
openAlbum();
}
}
});
}

/**
* 打开相册
*/
private void openAlbum() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent,CHOOSE_PHOTO);
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
openAlbum();
}else {
ToastUtils.showShort("你拒绝了权限请求");
}
break;

default:
break;
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case TAKE_PHOTO:
if (resultCode == RESULT_OK){
try{
// 将拍摄的照片显示出来
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
iv_show_photo.setImageBitmap(bitmap);
}catch (FileNotFoundException e){
e.printStackTrace();
}
}

case CHOOSE_PHOTO:
if (resultCode == RESULT_OK){
// 判断手机系统版本号
if (Build.VERSION.SDK_INT >= 19){
// 4.4及以上系统使用这个方法处理图片
handleImageOnKitKat(data);
}else {
// 4.4以下系统使用这个方法处理图片
handleImageBeforeKitKat(data);
}
}

default:
break;
}
}

/**
* 4.4及以上系统处理图片方法
* @param data
*/
@TargetApi(19)
private void handleImageOnKitKat(Intent data) {
String imagePath = null;
Uri uri = data.getData();
if (DocumentsContract.isDocumentUri(this,uri)){
// 若是document类型的Uri,则通过document id 处理
String docId = DocumentsContract.getDocumentId(uri);
if ("com.android.providers.media.documents".equals(uri.getAuthority())){
String id = docId.split(":")[1];// 解析出数字格式的id
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
}else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())){
Uri contentUri = ContentUris.withAppendedId(Uri.parse(
"content://downloads/public_downloads"),Long.valueOf(docId));
imagePath = getImagePath(contentUri,null);
}
}else if ("content".equalsIgnoreCase(uri.getScheme())){
// 若是content类型的Uri,则使用普通方法处理
imagePath = getImagePath(uri,null);
}else if ("file".equalsIgnoreCase(uri.getScheme())){
// 若是file类型到1Uri,则直接获取图片路径即可
imagePath = uri.getPath();
}
// 根据图片路径显示图片
displayImage(imagePath);
}

/**
* 4.4以下系统处理图片方法
* @param data
*/
private void handleImageBeforeKitKat(Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(uri,null);
displayImage(imagePath);
}

/**
* 通过Uri和selection来获取真实的图片路径
* @param uri
* @param selection
* @return
*/
private String getImagePath(Uri uri,String selection){
String path = null;
Cursor cursor = getContentResolver().query(uri,null,selection,null,null);
if (cursor != null){
if (cursor.moveToFirst()){
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}

/**
* 根据图片路径显示图片
* @param imagePath
*/
private void displayImage(String imagePath){
if (imagePath != null){
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
iv_show_photo.setImageBitmap(bitmap);
}else {
ToastUtils.showShort("选取图片失败");
}
}
}

  上述代码中,调用摄像头拍照时用到了内容提供器,所以别忘了在 AndroidManifest.xml 中对内容提供器进行注册,如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- android:name 属性的值是固定的
android:authorities 属性的值要和FileProvider.getUriForFile()方法中的第二个参数一致
<meta-data>来指定Uri的共享路径 -->
<provider
android:authorities="com.wonderful.myfirstcode.chapter8.TakePhotoActivity.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

  上面引用了一个 @xml/file_paths 资源是不存在的,需要另外创建它。新建 file_paths.xml 文件,内容如下:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- external_path指定Uri共享,其name的值可随便填,path是指共享的具体路径 -->
<external_path name = "my_images" path = ""/>
</paths>

  当然也别忘了声明访问SD卡的权限:

1
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

  注意事项:在实际开发中最好根据项目的需求先对照片进行适当的压缩,然后再加载到内存中。

8.3 播放多媒体文件

  在安卓中播放音频文件一般用 MediaPlayer 类来实现,播放视频文件主要用 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
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
public class MediaPlayActivity extends AppCompatActivity implements View.OnClickListener {

private Button music_play,music_pause,music_stop;// 音频播放、暂停、停止
private Button video_play,video_pause,video_replay;// 视频播放、暂停、重新播放

// 音频播放 创建MediaPlayer实例
private MediaPlayer mediaPlayer = new MediaPlayer();

// 视频播放显示
private VideoView videoView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media_play);

initMediaPlayerView();

if (ActivityCompat.checkSelfPermission(MediaPlayActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MediaPlayActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}else {
initMediaPlayer(); // 初始化MediaPlayer
initVideoPath(); // 初始化MediaPlayer
}
}



@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
initMediaPlayer();
initVideoPath();
}else {
ToastUtils.showShort("你拒绝了权限请求");
}
break;

default:
break;
}
}

/**
* 初始化音频播放器
*/
private void initMediaPlayer() {
File file = new File(Environment.getExternalStorageDirectory(),"music.mp3");//事先放好的音频文件
try {
mediaPlayer.setDataSource(file.getPath()); // 指定音频文件的路径
mediaPlayer.prepare();// 让 MediaPlayer 进入到准备状态
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 初始化视频播放器
*/
private void initVideoPath() {
File file = new File(Environment.getExternalStorageDirectory(),"movie.mp4");//事先放好的视频文件
videoView.setVideoPath(file.getPath()); // 指定视频文件的路径
}


private void initMediaPlayerView() {
music_play = (Button) findViewById(R.id.music_play);
music_pause = (Button) findViewById(R.id.music_pause);
music_stop = (Button) findViewById(R.id.music_stop);

music_play.setOnClickListener(this);
music_pause.setOnClickListener(this);
music_stop.setOnClickListener(this);

videoView = (VideoView) findViewById(R.id.video_view);
video_play = (Button) findViewById(R.id.video_play);
video_pause = (Button) findViewById(R.id.video_pause);
video_replay = (Button) findViewById(R.id.video_replay);

video_play.setOnClickListener(this);
video_pause.setOnClickListener(this);
video_replay.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()){
// 音频播放
case R.id.music_play:
if (!mediaPlayer.isPlaying()){
mediaPlayer.start(); // 开始播放
}
break;
case R.id.music_pause:
if (!mediaPlayer.isPlaying()){
mediaPlayer.pause(); // 暂停播放
}
break;
case R.id.music_stop:
if (!mediaPlayer.isPlaying()){
mediaPlayer.stop(); // 停止播放
}
break;

// 视频播放
case R.id.video_play:
if (!videoView.isPlaying()){
videoView.start(); // 开始播放
}
break;
case R.id.video_pause:
if (!videoView.isPlaying()){
videoView.pause(); // 暂停播放
}
break;
case R.id.video_replay:
if (!videoView.isPlaying()){
videoView.resume(); // 重新播放
}
break;
}

}

@Override
protected void onDestroy() {
super.onDestroy();
if (mediaPlayer != null){
mediaPlayer.stop();
mediaPlayer.release();// 释放资源
}
if (videoView != null){
videoView.suspend(); // 释放资源
}
}
}

  当然别忘了声明权限:

1
2
<!-- 访问SD卡权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

  本章内容相对简单,下一章将学习网络编程的知识。

Jason Xu wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!