0%

第8章:丰富你的程序-使用手机多媒体

使用通知

我们可以在Activity中、BroadcastReceiver以及Service中创建通知,不论在哪里创建,整体步骤是相同的,下面通过示例演示:

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
//1、需要NotificationManager管理通知,通过调用Context的getSystemService方法获得
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

//2、创建一个延迟意图(PendingIntent),标明点击notification时的响应,这里可以启动Activity,Broadcast以及service等
//PendingIntent有点类似于Intent,不过前者倾向于在某个合适的时机去执行某个动作,而后者倾向于立即执行某个动作
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
//根据启动的对象(Activity、Broadcast或service),可以使用getActivity()/getBroadcast()/getService()
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);


//3、通过Builder构造器创建Notification对象,几乎Android每个版本都会对通知这部分进行修改,因此我们需要使用
//support-v4包提供的NotificationCompat类来兼容性地实现,保证在各个版本上都能正常使用通知
Notification notification = new NotificationCompat.Builder(context)
.setContentTitle("title")
.setContentText("content")
.setSound(Uri.from(""))//控制通知的声音
//设置通知来的时候震动,数组中的值为时长,单位为毫秒,下标0表示手机静止时长,下标1为手机震动时长,下标2为手机静止
//时长,以此类推,这就实现了通知来时立刻震动1秒,静止1秒,再震动1秒
//注意震动需要权限 <uses-permission android:name="android.permission.VIBRATE">
.setVibrate(new long[]{0,1000,1000,1000})
.setWhen(System.currentMillis())//指定通知被创建的时间,下拉时这个时间会显示在通知上
.setSmallIcon(R.drawable.small_icon)//显示在顶部状态栏上的图标
.setLargeIcon(BitmapFactory.decodeResource(gerResources(),R.drawable.large_icon))//下拉时显示在通知左边
.setContentIntent(pi)//指明点击之后的意图
//通知自动消失,第二种取消方式是,将notification的id传入SecondActivity中,在进入到SecondActivity后,在SecondActivity
//的onCreate方法中重新获取manager,并且关闭这个通知:
//NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); manager.cancel(id);
.setLights(Color.GREEN,1000,1000)//设置灯光绿色和一闪一闪的效果
.setAutoCancel(true)
//设置style,一般通知只会显示很短的内容文字,但如果真的非常需要长文字,也是支持的,这样设置style,如果要显示一张大图片,
//以下换成NotificationCompat.BigTextStyle().bigPicture(bitmap)即可
.setStyle(new NotificationCompat.BigTextStyle().bigText("fdasfdsafdsafdafdasfsdafadsfdsfasdffasdfdsfdsfsda"))
//设置通知优先级,如果设置为最高的话,即要求用户立刻看,不会像普通通知只在状态栏显示一个图标,而是弹出一个横幅
//不论你当前在玩游戏还是看电影,这个横幅都会弹
.setPriority(NotificationCompat.PRIORITY_MAX)
.build();

//4、发出通知
manager.notify(1,notification);//第一个参数指定notification的id


调用摄像头和相册

平时使用QQ或者微信的时候经常要别人分享图片,这些图片可以使手机摄像头拍摄也可以从相册中选取,这种功能非常普遍。

摄像头拍照

直接上代码展示可能更加清晰:

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
takePhoto.setOnclickListener(new View.OnclickListener(){
@Override
public void onClick(View v){
//创建File对象,用于存储拍照后的图片
File outputImage = new File(getExternalCacheDir(),"output.jpg");
try{
if(outputImage.exists()){//存在了
outputImage.delete();
}
outputImage.createNewFile();
}catch(IOException e){

}

if(Build.VERSION.SDK_INT >= 24){
imageUri = FileProvider.getUriForFile(MainActivity.this,
"com.example.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);
}
});


@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));
ivPic.setImageBitmap(bitmap);
}catch (FileNotFoundException e){

}
}

break;
}
}

上面的代码中我们用了内容提供器,因此还需要在AndroidManifest.xml中声明这个提供器(有一点要注意的是,在4.4以前(4.4及以后不需要)访问SD卡得应用关联目录也是要声明权限的,为了兼容老版本的手机,需要声明 WRITE_EXTERNAL_STORAGE 权限):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<users-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:icon="@mipmap/ic_launcher"
...
android:theme="@style/AppTheme">

<privider
<!--这里,android:name属性的值是固定的,android:authorities属性的值必须要和刚才FileProvider.getUriForFile()-->
<!--方法中的第二个参数一致,另外,meta-data中用resource指定了Uri的共享路径-->
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>

</application>

当然,provider声明中使用了@xml/file_paths资源,这个资源我们还没创建,因此在res目录下可以创建这么个xml,内容如下:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8">
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--这里面external-path指定Uri共享的,name属性随便填,path属性表示共享的具体路径,这里设置空值就表示将整个SD卡进行共享-->
<!--当然,你可以仅仅共享我们存放output.jpg这张图片的路径-->
<external-path name="my_images" path="">
</paths>

以上整个代码首先创建了一个File对象,用于存放摄像头拍下的图片,我们将其命名为output.jpg,并将它存放在手机SD卡的应用关联缓存目录(指SD卡中专门用于存放当前应用缓存数据的位置,路径为/sdcard/Android/data//cache,调用getExternalCacheDir()方法就可以得到这个目录)下。为什么使用应用关联缓存目录来存放图片呢?因为从Android 6.0开始,读写SD卡被列为危险权限,如果将图片存放SD卡得任何其他目录,都要进行运行时权限处理才行,而使用应用关联目录则可以跳过这一步

接着会判断如果设备版本低于7.0,就调用Uri.fromFile()方法将File对象转换为Uri对象,这个Uri标识着图片的本地真实路径。否则就调用FileProvider的getUriForFile()方法获得Uri对象。之所以这样是因为从7.0开始,直接使用本地真实路径的Uri被认为是不安全的,会抛出异常,而FileProvider则是一种特殊的内容提供器,可以选择性地将封装过的Uri共享给外部,提高应用安全性。

最后就是启动摄像头拍照并且回调获取图片了。

从相册中选择照片

直接选取一张现有图片比打开相机拍一张照片更加常用,一个优秀的应用应该将这两种方式都提供给用户。废话不多说直接上代码:

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
chooseFromAlbum.setOnclickListener(new View.OnclickListener(){
@Override
public void onClick(View v){
if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){

ActivityCompat.requestPermissions(MainActivity.this,new String[]{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){
swithc(requestCode){
case 1:
if(grantResults.lenght > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
openAlbum();
}else{
Toast.makeText(this,"you denied the permission",Toast.LENGTH_SHORT).show();
}

break;
}
}


@Override
protected void onActivityResult(int requestCode,int resultCode,Intent data){
switch(requestCode){
case CHOOSE_PHOTO:
if(resultCode == RESULT_OK){
if(Build.VERSION.SDK_INT >= 19){//4.4及以上
handleImageOnKitKat(data);
}else{//4.4以下
handleImageBeforeKitKat(data);
}

}

break;
}
}

//4.4及以上处理方式
@TargetApi(19)
private void handleImageOnKitKat(Intent data){
String imagePath = null;
Uri uri = data.getData();
//如果是document类型Uri,则通过document id处理
if(DocumentsContract.isDocumentUri(this,uri)){
String docId = DocumentsContract.getDocumentId(uri);
if("com.android.providers.media.documents".equals(uri.getAuthority()){
//解析出数字格式的id
String selection = MediaStore.Images.Media._ID + "=" + id;
imagePath = getImagePaht(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".eualsIgnoreCase(uri.getScheme())){//如果是file类型的uri,直接获取推按路径即可
imagePath = uri.getPath();
}

//根据路径显示图片
displayImage(imagePath);
}

//4.4以前处理方式
private void handleImageBeforeKitKat(Intent data){
Uri uri = data.getData();
//因为他的Uri没有封装过的,不需要任何解析直接去获取真实路径即可
String imagePath = getImagePath(uri,null);
displayImage(imagePath);
}


//通过Uri和selection来获取真实的图片路径
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;
}

//根据路径显示图片
private void displayImage(String imagePath){
if(imagePath != null){
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
ivPicture.setImageBitmap(bitmap);
}else{
Toast.makeText(this,"failed to get image",Toast.LENGTH_SHORT).show();
}
}

因为照片是存在SD卡上的,所以我们首先进行权限处理,WRITE_EXTERNAL_STORAGE表示授予了对SD卡的读和写的能力。在onActivityResult回调中针对不同版本使用不同方式处理图片,因为从4.4开始,选取相册中的图片不再返回真实的Uri了,而是一个封装过的Uri,因此必须对这个Uri解析才行,在handleImageOnKitKat()方法中,如果返回的Uri是document类型的话,就取出document id进行处理,如果Uri的authority是media格式的话,document id还需要进行一次解析,要通过字符串分割的方式取出后半部分才能得到真正的数字id。

播放多媒体文件

播放音频和视频比较简单,没有兼容性等复杂问题,仅仅只需要记住:

  1. 申请 WRITE_EXTENAL_STORAGE 权限

  2. 使用 MediaPlayer 播放音频结束时,在 onDestroy方法中要进行 MediaPlayer.stop() 和 MediaPlayer.release() ,将资源释放掉;

  3. 使用 VideoView 播放视频结束时,在 onDestroy方法中要进行 VideoView.suspend() ,将资源释放掉;

其他内容略过。

谢谢你的鼓励