Android手机多媒体
一、通知的基本用法
无论是在哪里创建通知,整体的步骤都是相同的。
首先需要一个 NotificationManager 来对通知进行管理,可以调用Context的 getSystemService() 方法获取到。getSystemService()方法接收一个字符串参数用于确定获取系统的哪个服务,这里传入Context.NOTIFICATION_SERVICE即可。
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
接下来需要使用一个Builder构造器来创建Notification对象,几乎Android系统的每一个版本都会对通知这部分功能进行或多或少的修改,API不稳定性问题在通知上面突显得尤其严重。使用support库中提供的兼容API能很好地解决这个问题。
Notification notification = new NotificationCompat.Builder(context).build();
上述代码只是创建了一个空的Notification对象,并没有什么实际作用,我们可以在最终的build()方法之前连缀任意多的设置方法来创建一个丰富的Notification对象,
Notification notification = new NotificationCompat.Builder(context)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.large_icon))
.build();
以上工作都完成之后,只需要调用NotificationManager的notify()方法就可以让通知显示出来了。notify()方法接收两个参数:
- 第一个参数是id,要保证为每个通知所指定的id都是不同的。
- 第二个参数则是Notification对象。
manager.notify(1, notification);
要想实现通知的点击效果,还需要在代码中进行相应的设置,这就涉及了一个新的概念:PendingIntent。PendingIntent主要提供了几个静态方法用于获取PendingIntent的实例,可以根据需求来选择是使用getActivity()方法、getBroadcast()方法,还是getService()方法。这几个方法所接收的参数都是相同的:
- 第一个参数依旧是Context,不用多做解释。
- 第二个参数一般用不到,通常都是传入0即可。
- 第三个参数是一个Intent对象,我们可以通过这个对象构建出PendingIntent的“意图”。
- 第四个参数用于确定PendingIntent的行为,有FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT这4种值可选,每种值的具体含义你可以查看文档,通常情况下这个参数传入0就可以了。
FLAG_CANCEL_CURRENT
:如果NotificationManager管理的PendingIntent已经存在,那么将会取消当前的PendingIntent,从而创建一个新的PendingIntent。
FLAG_UPDATE_CURRENT
:如果NotificationManager管理的PendingIntent已经存在,可以让新的Intent更新之前PendingIntent中的Intent对象数据,例如更新Intent中的Extras,另外,我们也可以在PendingIntent的原进程中调用PendingIntent的cancel ()把其从系统中移除掉。
FLAG_NO_CREATE
:如果NotificationManager管理的PendingIntent已经存在,那么将不进行任何操作,直接返回已经存在的PendingIntent,如果PendingIntent不存在了,那么返回null。
FLAG_ONE_SHOT
:相同的PendingIntent只能使用一次,且遇到相同的PendingIntent时不会去更新PendingIntent中封装的Intent的extra部分的内容。
Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
NotificationManager manager = (NotificationManager) getSystemService
(NOTIFICATION_SERVICE);
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)
.build();
manager.notify(1, notification);
如果没有在代码中对该通知进行取消,它就会一直显示在系统的状态栏上。解决的方法有两种,一种是在NotificationCompat.Builder中再连缀一个setAutoCancel()方法,一种是显式地调用NotificationManager的cancel()方法将它取消,两种方法我们都学习一下。
第一种写法:
Notification notification = new NotificationCompat.Builder(this)
...
.setAutoCancel(true)
.build();
第二种写法:
NotificationManager manager = (NotificationManager) getSystemService
(NOTIFICATION_SERVICE);
manager.cancel(1);
通知设置的id是1,如果想取消哪条通知,在cancel()方法中传入该通知的id就行了。
NotificationCompat.Builder中的setSound()方法,它可以在通知发出的时候播放一段音频。setSound()方法接收一个Uri参数,所以在指定音频文件的时候还需要先获取到音频文件对应的URI。
Notification notification = new NotificationCompat.Builder(this)
...
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg")))
.build();
让手机进行振动,使用的是vibrate这个属性。它是一个长整型的数组,用于设置手机静止和振动的时长,以毫秒为单位。下标为0的值表示手机静止的时长,下标为1的值表示手机振动的时长,下标为2的值又表示手机静止的时 长,以此类推。所以,如果想要让手机在通知到来的时候立刻振动1秒,然后静止1秒,再振动1秒,
Notification notification = new NotificationCompat.Builder(this)
...
.setVibrate(new long[] {0, 1000, 1000, 1000 })
.build();
想要控制手机振动还需要声明权限
<uses-permission android:name="android.permission.VIBRATE" />
setLights()方法接收3个参数,
- 第一个参数用于指定LED灯的颜色,
- 第二个参数用于指定LED灯亮起的时长,以毫秒为单位,
- 第三个参数用于指定LED灯暗去的时长,也是以毫秒为单位。
Notification notification = new NotificationCompat.Builder(this)
...
.setLights(Color.GREEN, 1000, 1000)
.build();
如果不想进行那么多繁杂的设置,也可以直接使用通知的默认效果,它会根据当前手机的环境来决定播放什么铃
声,以及如何振动,
Notification notification = new NotificationCompat.Builder(this)
...
.setDefaults(NotificationCompat.DEFAULT_ALL)
.build();
setStyle()这个方法允许构建出富文本的通知内容。setStyle()方法接收一个NotificationCompat.Style参数,这个参数就是用来构建具体的富文本信息的,如长文字、图片等。
Notification notification = new NotificationCompat.Builder(this)
...
.setStyle(new 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()方法中创建了一个setStyle()NotificationCompat.BigTextStyle对象,这个对象就是用于封装长文字信息的,调用它的setStyle()bigText()方法并将文字内容传入就可以了。
除了显示长文字之外,通知里还可以显示一张大图片,具体用法也是基本相似的:
Notification notification = new NotificationCompat.Builder(this)
...
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.big_image)))
.build();
调用的setStyle()方法,这次我们在参数中创建了一个NotificationCompat.BigPictureStyle对象,这个对象就是用于设置大图片的,然后调用它的bigPicture()方法并将图片传入。通过BitmapFactory的decodeResource()方法将图片解析成Bitmap对象,再传入到bigPicture()方法中就可以了。
setPriority()方法,它可以用于设置通知的重要程度。setPriority()方法接收一个整型参数用于设置这条通知的重要程度,一共有5个常量值可选:
- PRIORITY_DEFAULT:表示默认的重要程度,和不设置效果是一样的;
- PRIORITY_MIN:表示最低的重要程度,系统可能只会在特定的场景才显示这条通知,比如用户下拉状态栏的时候;
- PRIORITY_LOW:表示较低的重要程度,系统可能会将这类通知缩小,或改变其显示的顺序,将其排在更重要的通知之后;
- PRIORITY_HIGH:表示较高的重要程度,系统可能会将这类通知放大,或改变其显示的顺序,将其排在比较靠前的位置;
- PRIORITY_MAX:表示最高的重要程度,这类通知消息必须要让用户立刻看到,甚至需要用户做出响应操作。
Notification notification = new NotificationCompat.Builder(this)
...
.setPriority(NotificationCompat.PRIORITY_MAX)
.build();
二、调用摄像头和相册
(一)调用摄像头拍照
Button是用于打开摄像头进行拍照的,而ImageView则是用于将拍到的图片显示出来。
public class MainActivity extends AppCompatActivity {
public static final int TAKE_PHOTO = 1;
private ImageView picture;
private Uri imageUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button takePhoto = (Button) findViewById(R.id.take_photo);
picture = (ImageView) findViewById(R.id.picture);
takePhoto.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();
}
if (Build.VERSION.SDK_INT >= 24) {
imageUri = FileProvider.getUriForFile(MainActivity.this,
"com.example.cameraalbumtest.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));
picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
}
通过Context.getExternalFilesDir()方法可以获取到 SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据。
通过Context.getExternalCacheDir()方法可以获取到 SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据,如果使用上面的方法,当你的应用在被用户卸载后,SDCard/Android/data/你的应用的包名/ 这个目录下的所有文件都会被删除,不会留下垃圾信息。
上面二个目录分别对应 设置->应用->应用详情里面的”清除数据“与”清除缓存“选项,如果要保存下载的内容,就不要放在以上目录下。
什么叫作应用 关联缓存目录 呢?就是指SD卡中专门用于存放当前应用缓存数据的位置,调用getExternalCacheDir() 方法可以得到这个目录,具体的路径是/sdcard/Android/data//cache。那么为什么要使用应用关联缓目录来存放图片呢?因为从Android 6.0系统开始,读写SD卡被列为了危险权限,如果将图片存放在SD卡的任何其他目录,都要进行运行时权限处理才行,而使用应用关联目录则可以跳过这一步。接着会进行一个判断,如果运行设备的系统版本低于Android 7.0,就调用Uri的 fromFile() 方法将File对象转换成Uri对象,这个Uri对象标识着output_image.jpg这张图片的本地真实路径。否则,就调用FileProvider的 getUriForFile() 方法将File对象转换成一个封装过的Uri对象。
getUriForFile()方法接收3个参数,
- 第一个参数要求传入Context对象,
- 第二个参数可以是任意唯一的字符串,
- 第三个参数则是我们刚刚创建的File对 象。
注:之所以要进行这样一层转换,是因为从Android 7.0系统开始,直接使用本地真实路径的Uri被认为是不安全的,会抛出一个FileUriExposedException异常。而FileProvider则是一种特殊的内容提供器,它使用了和内容提供器类似的机制来对数据进行保护,可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性
由于用到了内容提供器,那么自然要在AndroidManifest.xml中对内容提供器进行注册了
<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="android.support.v4.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>
android:name属性的值是固定的。android:authorities属性的值必须要和刚才FileProvider.getUriForFile()方法中的第二个参数一致。这里还在 <provider>
标签的内部使用 <metadata>
来指定Uri的共享路径,并引用了一个@xml/file_paths资源。当然,这个资源现在还是不存在的,下面我们就来创建它。
右击res目录→New→Directory,创建一个xml目录,接着右击xml目录→New→File,创建一个file_paths.xml文件。
<?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这张图片的路径。从4.4系统开始不再需要权限声明。那么我们为了能够兼容老版本系统的手机,还需要在AndroidManifest.xml中声明一下访问SD卡的权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
(二)从相册中选择照片
public class MainActivity extends AppCompatActivity {
...
public static final int CHOOSE_PHOTO = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button takePhoto = (Button) findViewById(R.id.take_photo);
Button chooseFromAlbum = (Button) findViewById(R.id.choose_from_album);
...
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) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openAlbum();
} else {
Toast.makeText(this, "You denied the permission",
Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
@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;
default:
break;
}
}
@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类型的Uri,直接获取图片路径即可
imagePath = uri.getPath();
}
displayImage(imagePath); // 根据图片路径显示图片
}
private void handleImageBeforeKitKat(Intent data) {
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;
}
private void displayImage(String imagePath) {
if (imagePath != null) {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
picture.setImageBitmap(bitmap);
} else {
Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show();
}
}
}
三、播放多媒体文件
(一)播放音频
在Android中播放音频文件一般都是使用MediaPlayer类来实现的,下表列出了MediaPlayer类中一些较为常用的控制方法。
方法名 | 功能描述 |
---|---|
setDataSource() | 设置要播放的音频文件的位置 |
prepare() | 在开始播放之前调用这个方法完成准备工作 |
start() | 开始或继续播放音频 |
pause() | 暂停播放音频 |
reset() | 将MediaPlayer对象重置到刚刚创建的状态 |
seekTo() | 从指定的位置开始播放音频 |
stop() | 停止播放音频。调用这个方法后的MediaPlayer对象无法再播放音频 |
release() | 释放掉与MediaPlayer对象相关的资源 |
isPlaying() | 判断当前MediaPlayer是否正在播放音频 |
getDuration() | 获取载入的音频文件的时长 |
MediaPlayer的工作流程:
- 首先需要创建出一个MediaPlayer对象;
- 然后调用setDataSource()方法来设置音频文件的路径;
- 再调用prepare()方法使MediaPlayer进入到准备状态;
- 接下来调用start()方法就可以开始播放音频;
- 调用pause()方法就会暂停播放;
- 调用reset()方法就会停止播放。
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private MediaPlayer mediaPlayer = new MediaPlayer();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button play = (Button) findViewById(R.id.play);
Button pause = (Button) findViewById(R.id.pause);
Button stop = (Button) findViewById(R.id.stop);
play.setOnClickListener(this);
pause.setOnClickListener(this);
stop.setOnClickListener(this);
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 {
initMediaPlayer(); // 初始化MediaPlayer
}
}
private void initMediaPlayer() {
try {
File file = new File(Environment.getExternalStorageDirectory(),
"music.mp3");
mediaPlayer.setDataSource(file.getPath()); // 指定音频文件的路径
mediaPlayer.prepare(); // 让MediaPlayer进入到准备状态
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
initMediaPlayer();
} else {
Toast.makeText(this, "拒绝权限将无法使用程序",
Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.play:
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start(); // 开始播放
}
break;
case R.id.pause:
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause(); // 暂停播放
}
break;
case R.id.stop:
if (mediaPlayer.isPlaying()) {
mediaPlayer.reset(); // 停止播放
initMediaPlayer();
}
break;
default:
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
}
}
不要忘记在AndroidManifest.xml文件中声明用到的权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
(二)播放视频
VideoView的用法和MediaPlayer也比较类似。
方法 | 功能描述 |
---|---|
setVideoPath() | 设置要播放的视频文件的位置 |
start() | 开始或继续播放视频 |
pause() | 暂停播放视频 |
resume() | 将视频重头开始播放 |
seekTo() | 从指定的位置开始播放视频 |
isPlaying() | 判断当前是否正在播放视频 |
getDuration() | 获取载入的视频文件的时长 |
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private VideoView videoView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
videoView = (VideoView) findViewById(R.id.video_view);
Button play = (Button) findViewById(R.id.play);
Button pause = (Button) findViewById(R.id.pause);
Button replay = (Button) findViewById(R.id.replay);
play.setOnClickListener(this);
pause.setOnClickListener(this);
replay.setOnClickListener(this);
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 {
initVideoPath(); // 初始化VideoView
}
}
private void initVideoPath() {
File file = new File(Environment.getExternalStorageDirectory(), "movie.mp4");
videoView.setVideoPath(file.getPath()); // 指定视频文件的路径
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
initVideoPath();
} else {
Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).
show();
finish();
}
break;
default:
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.play:
if (!videoView.isPlaying()) {
videoView.start(); // 开始播放
}
break;
case R.id.pause:
if (videoView.isPlaying()) {
videoView.pause(); // 暂停播放
}
break;
case R.id.replay:
if (videoView.isPlaying()) {
videoView.resume(); // 重新播放
}
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (videoView != null) {
videoView.suspend();
}
}
}
要记得在AndroidManifest.xml文件中声明用到的权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />