第八章主要讲了手机中的多媒体技术
一、通知
通知的基本用法:
通知(Notification)是 Android 系统一个比较有特色的功能,当某个应用程序希望向用户发出一些提示信息,而该程序又不在前台运行时,就可以借助通知来实现。
通知既可以在活动中创建,也可以在广播接收器里创建,还可以在服务中创建。
/*
首先获取一个 NotificationManager 来对通知进行管理
*/
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
/*
这里请使用 Support 包下的 NotificationCompat.Builder()来创建一个 Notification 对象,
因为几乎 Android 系统每一个版本都会对通知这部分功能进行修改,API 很不稳定,所以使用兼容包
下的 NotificationCompat 来兼容。
*/
Notification notification = new NotificationCompat.Builder(SecondActivity.this)
.setContentTitle("this is title")//设置通知的标题内容
.setContentText("this is text")//设置通知的正文内容
.setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
.setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
.build();
/*
让通知显示出来
参1:id,保证每个通知所指定的 id 都是唯一的
参2:是 Notification 对象
*/
manager.notify(1, notification);
注意:这个时候如果点击这条通知,还没有任何效果,如果要有相应的点击效果,比如跳转界面,就需要使用 PendingIntent 。PendingIntent和Intent的区别是:Intent 更倾向于立即去执行某个动作,而 PendingIntent 更倾向于在某个合适的时机去执行某个动作。
pendingIntent 用法很简单,主要提供了几个静态的方法用于获取 PendingIntent,根据需求来选择使用 getActivity()方法、getBroadcast()方法、getService()方法
Intent intent = new Intent(SecondActivity.this,MainActivity.class);
/*
参1:是 Context
参2:一般用不到,通常都是传入0
参3:是一个 Intent 对象
参4:确定 PendingIntent 的行为,
1.FLAG_ONE_SHOT:相同的 PendingIntent 只能使用一次,且遇到相同的PendingIntent时
不会去更新 PendingIntent 中封装的意图的额外部分内容
2.FLAG_NO_CREATE:如果创建的 PendingIntent 尚未存在,则不创建新的 PendingIntent,
直接返回空
3.FLAG_CANCEL_CURRENT:如果要创建的 PendingIntent 已存在,那么在创建
新的PendingIntent之前,原先已存在的 PendingIntent
中的意图将不能使用
4.FLAG_UPDATE_CURRENT:如果要创建的 PendingIntent 已经存在,保留原先的 PendingIntent
的同时,将原先 PendingIntent 中封装的意图中的多余部分替换为现在
新创建的的PendingIntent的意向中额外的内容
5,通常传入0即可:不打算使用任何一种标志来控制PendingIntent 的创建
*/
PendingIntent pendingIntent = PendingIntent.getActivity(SecondActivity.this,0,intent,0);
那么,创建通知的代码就可以更改为
Intent intent = new Intent(this,NotificationActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
/*
首先获取一个 NotificationManager 来对通知进行管理
*/
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
/*
这里请使用 Support 包下的 NotificationCompat.Builder()来创建一个 Notification 对象,
因为几乎 Android 系统每一个版本都会对通知这部分功能进行修改,API 很不稳定,所以使用兼容包
下的 NotificationCompat 来兼容。
*/
Notification notification = new NotificationCompat.Builder(SecondActivity.this)
.setContentTitle("this is title")//设置通知的标题内容
.setContentText("this is text")//设置通知的正文内容
.setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
.setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
.setContentIntent(pendingIntent)//用户点击通知要执行的意图
.setAutoCancel(true)//点击通知后,通知会自动消失
.build();
/*
通知自动消失也可以这样写,这个1,就是我们调用
notify()方法时候传入的第一个参数,也就是通知的唯一标示:id,
你想要取消哪条通知,就传入通知的 id 即可
*/
// manager.cancel(1);
/*
让通知显示出来
参1:id,保证每个通知所指定的 id 都是唯一的
参2:是 Notification 对象
*/
manager.notify(1, notification);
通知的进阶技巧:
这里说几个比较常见的 API:
- setSound():在通知发出的时候播放一段音频,在指定音频文件的时候需要先获取音频文件对应的 URI
- setVibrate():在通知到来的时候让手机进行震动。参数是一个长整型数组,用于设置手机静止和震动的时长,以毫秒为单位。下标为0或偶数表示手机静止的时长,下标为奇数表示手机震动的时长。不过记得需要申请权限
- setLights():设置手机的 LED 灯,该方法接受三个参数,参1指定 LED 灯的颜色,参2指定灯亮起的时长,参3表示灯暗去的时长,都是以毫秒为单位。
- setDefaults(NotificationCompat.DEFAULT_ALL):该方法用于设置通知的默认效果,它会根据当前的手机环境来决定播放什么铃声,如何震动等。
Notification notification = new NotificationCompat.Builder(SecondActivity.this)
.setContentTitle("this is title")//设置通知的标题内容
.setContentText("this is text")//设置通知的正文内容
.setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
.setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
.setContentIntent(pendingIntent)//用户点击通知要执行的意图
.setAutoCancel(true)//点击通知后,通知会自动消失
.setSound(Uri.fromFile(new File("/System/media/audio/ringtones/Luna.ogg")))//设置声音
// 震动不要忘了权限:
.setVibrate(new long[]{0,1000,1000,1000})//设置震动,
.setLights(Color.GREEN,1000,1000)//设置 LED 灯
//.setDefaults(NotificationCompat.DEFAULT_ALL)//默认效果
.build();
通知的高级功能
通知还有许多高级功能:
- setStyle():该方法允许我们构建出富文本的通知内容,也就是说,通知中不光可以有文字和图标,还可以包含更多的东西,setStyle()方法接收一个 NotificationCompat.Style 参数,这个参数就是从来构建具体的富文本信息的,如长文字、图片等。
如果我们使用 setContentText()方法设置比较长的文字,通知内容是无法显示完整的,多余的部分会用省略号来代替,如果要显示长文字,需要使用 setStyle()方法。
Notification notification = new NotificationCompat.Builder(SecondActivity.this)
.setContentTitle("this is title")//设置通知的标题内容
.setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
.setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
.setContentIntent(pendingIntent)//用户点击通知要执行的意图
.setAutoCancel(true)//点击通知后,通知会自动消失
.setDefaults(NotificationCompat.DEFAULT_ALL)//默认效果
/*
通过 setStyle 方法,即使通知内容过长,也能全部展示出来
*/
.setStyle(new NotificationCompat.BigTextStyle().bigText("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"))
.build();
- setStyle():除了显示长文字外,还可以展示一张大图
Notification notification = new NotificationCompat.Builder(SecondActivity.this)
.setContentTitle("this is title")//设置通知的标题内容
.setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
.setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
.setContentIntent(pendingIntent)//用户点击通知要执行的意图
.setAutoCancel(true)//点击通知后,通知会自动消失
.setDefaults(NotificationCompat.DEFAULT_ALL)//默认效果
/*
通过 setStyle 方法,展示一张大图
*/
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(
BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)))
.build();
- setPriority():设置通知的重要程度,该方法接收一个整型参数用于设置这条通知的重要程度,一共有5个常量值可选:
- PRIORITY_DEFAULT 表示默认的重要程度,和不设置效果一样;
- PRIORITY_MIN 表示最低的重要程度,系统只会在特定的场景才显示这条通知
- PRIORITY_LOW 表示较低的重要程度,系统可能会将这类通知缩小,或改变其显示顺序,将其排在更重要的通知之后
- PRIORITY_HIGH 表示较高的重要程度,系统可能会将这类通知放大,或改变其显示顺序,将其排在比较靠前的位置
- PRIORITY_MAX 表示最高的重要程度,这类通知消息必须要让用户立刻看到,甚至需要用户做出相应操作
Notification notification = new NotificationCompat.Builder(SecondActivity.this)
.setContentTitle("this is title")//设置通知的标题内容
.setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
.setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
.setContentIntent(pendingIntent)//用户点击通知要执行的意图
.setAutoCancel(true)//点击通知后,通知会自动消失
.setDefaults(NotificationCompat.DEFAULT_ALL)//默认效果
.setPriority(NotificationCompat.PRIORITY_MAX)//设置重要程度
.build();
二、调用摄像头和拍照
调用摄像头
public static final int TAKE_PHOTO = 1;
private Uri imageUri;
/*
创建 File 对象,用于存储拍照后的图片
getExternalCacheDir():SD 卡中专门用于存放当前应用缓存数据的位置
具体路径是:/sdcard/Android/data//cache
Android6.0开始,读写 sd 卡被列为了危险权限,需要动态申请,如果使用
应用关联的目录则可以不用动态申请。
*/
File fileImage = new File(getExternalCacheDir(), "output_image.jpg");
try {
if (fileImage.exists()) {
fileImage.delete();
}
fileImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
/*
如果设备的系统版本 >= Android7.0
将 File 对象转换成封装过的 URI 对象
FileProvider.getUriForFile()方法接受三个参数:
参1:Context 对象
参2:任意字符串,(但是要和后面所说的 清单文件中的 Provider 中的 authorities 所匹配)
参3:File 对象
注意:因为从 Android7.0开始,直接使用本地真实路径的 URI 被认为是不安全的,会抛出一个
FileUriExposedException 异常,而 FileProvider 则是一种特殊的内容提供器,它使用了和
内容提供器类似的机制来对数据进行保护,可以选择性的将封装过的 URI 共享给外部,从而提高了应用安全性
*/
if (Build.VERSION.SDK_INT >= 24) {
imageUri = FileProvider.getUriForFile(SecondActivity.this,
"com.example.cameraalbum.fileprovider", fileImage);
} else {
/*
将 File 转换成 URI 对象,这个 URI 对象包含着
图片本地真实路径
*/
imageUri = Uri.fromFile(fileImage);
}
/*
启动相机
*/
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
//指定图片输出地址
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
startActivityForResult(intent,TAKE_PHOTO);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode){
case TAKE_PHOTO:
if(resultCode == RESULT_OK){
try {
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
iv.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
注意:在 Android4.4系统之前,访问 SD 卡应用关联目录需要声明权限,从4.4系统开始就不再需要声明,为了兼容老版本,所以需要在清单文件中设置一下访问 SD 卡的权限。
uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
从相册中选择照片
/*
因为相册中的照片是存储在 SD 卡上的,要从 SD 卡中读取照片,就需要申请权限
该权限表示同时授予程序对 SD 卡读和写的能力
*/
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
openAlbum();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openAlbum();
} else {
Toast.makeText(this, "您决绝了请求", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
/**
* 打开相册
*/
private void openAlbum() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent, CHOOSE_PHOTO);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case CHOOSE_PHOTO:
if (resultCode == RESULT_OK) {
/*
判断手机的版本
因为从 Android4.4版本开始,选取相册中的图片不在返回图片真实的 Uri 了,
而是一个封装过的 Uri,因此4.4版本以上的手机需要对这个 Uri 进行解析才行
*/
if (Build.VERSION.SDK_INT >= 19) {
//4.4及以上的系统使用这个方法处理图片
handleImageOnKitKat(data);
} else {
//4.4以下的系统使用这个方法处理图片
handleImageBeforeKitKat(data);
}
}
break;
default:
break;
}
}
/**
* 4.4及以上的系统使用这个方法处理图片
*/
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())) {
/*
Uri 的 authority 是 media 格式的话,document id 还需要再次解析,
通过字符串分割的方式取出后半部分才能得到真正的数字 id
*/
String id = docId.split(":")[1];//解析出数字格式的 id
//取出的 id 用于构建新的 Uri 和条件语句
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 类型的 Uri,直接获取图片路径即可
imagePath = uri.getPath();
}
displayImage(imagePath);//根据图片路径显示图片
}
/**
* 4.4一下的系统使用这个方法处理图片
*/
private void handleImageBeforeKitKat(Intent data) {
//这种 Uri 是没有进行封装过的,不需要任何解析
Uri uri = data.getData();
String imagePath = getImagePath(uri, null);
displayImage(imagePath);
}
/**
* 获取图片的真实路径
*/
private String getImagePath(Uri uri, String selection) {
String path = null;
//通过 Uri 和 selection 来获取真实的图片路径
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 (!TextUtils.isEmpty(imagePath)) {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
iv.setImageBitmap(bitmap);
} else {
Toast.makeText(this, "获取图片失败", Toast.LENGTH_SHORT).show();
}
}
三、播放多媒体文件
播放音频
Android 中播放音频文件一般都是使用 MediaPlayer 类来实现的。
- setDataSource():设置要播放的音频文件的位置
- prepare():开始播放之前调用这个方法完成准备工作
- start():开始或继续播放音频
- pause():暂停播放音频
- reset():将 MediaPlayer 对象重置到刚刚创建的状态
- seekTo():从指定的位置开始播放音频
- stop():停止播放音频,调用后,MediaPlayer 对象无法再播放音频
- release():释放掉与 MediaPlayer 对象相关的资源
- isPlaying():判断当前 MediaPlayer 是否正在播放音频
- getDuration():获取载入的音频文件的时长
我们往手机里面导入一个音频文件
private MediaPlayer mediaPlayer = new MediaPlayer();
if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}else{
initMediaPlayer();
}
private void initMediaPlayer(){
try {
File file = new File(Environment.getExternalStorageDirectory(),"yzhaj.mp3");
mediaPlayer.setDataSource(file.getPath());//指定音频文件的路径:/storage/emulated/0
mediaPlayer.prepare();//让 MediaPlayer 进入准备状态
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
initMediaPlayer();
}else{
Toast.makeText(this, "您决绝了请求", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.tv_play:
if(mediaPlayer.isPlaying()){
mediaPlayer.pause();
}else{
mediaPlayer.start();
}
break;
case R.id.tv_pause:
if(mediaPlayer.isPlaying()){
mediaPlayer.pause();
}
break;
case R.id.tv_stop:
mediaPlayer.reset();
initMediaPlayer();
break;
default:
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// 千万不要忘了释放资源
if(mediaPlayer != null){
mediaPlayer.stop();
mediaPlayer.release();
}
}
//在清单文件中声明权限
播放视频
播放视频主要是使用 VideoView 来实现的
- setVideoPath():设置播放的视频文件的位置
- start():开始或继续播放视频
- pause():暂停播放视频
- resume():将视频重头开始播放
- seekTo():从指定位置开始播放视频
- isPlaying():判断当前是否正在播放视频
- getDuration():获取视频文件的时长
- suspend():释放所占用的资源
private void videoView(){
if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}else{
initVideoView();
}
}
private void initVideoView(){
File file = new File(Environment.getExternalStorageDirectory(),"video.mp4");
videoView.setVideoPath(file.getPath());//指定视频文件的路径
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
initVideoView();
}else{
Toast.makeText(this, "您决绝了权限", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.tv_play:
if(videoView.isPlaying()){
videoView.pause();
}else{
videoView.start();
}
break;
case R.id.tv_pause:
if(videoView.isPlaying()){
videoView.pause();
}
break;
case R.id.tv_replay:
videoView.resume();
break;
default:
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(videoView != null){
videoView.suspend();
}
}
//不要忘了声明权限
注意:VideoView 只是将 MediaPlayer 做了一层封装而已,而且 VideoView 并不是万能的视频播放器,它在视频格式的支持以及播放的效率方面都存在着较大的不足。
下一篇文章:https://www.jianshu.com/p/dc54268e0ef0