欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android学习羁绊之多媒体开发

程序员文章站 2022-05-16 20:26:22
...

原文链接:Android学习羁绊–>多媒体开发


手机在我们的生活中正扮演着越来越重要的角色,各种娱乐方式都可以在手机上进行。众多的娱乐方式少不了强大的多媒体功能的支持,而Android在这方面也做得非常出色。它提供了一系列的API,使得我们可以在程序中调用很多手机的多媒体资源,从而编写出更加丰富多彩的应用程序。

调用摄像头和相册

调用摄像头拍照

现在很多的应用都会要求用户上传一张图片来作为头像,这时打开摄像头拍张照是最简单快捷的。

启动摄像头

应用程序要启动手机的相机程序需要通过以下代码来实现:

// 启动相机程序
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, 1);
  • 使用Intent对象启动相机程序,Intent的action指定为android.media.action.IMAGE_CAPTURE
  • 调用Intent对象的putExtra() 方法将图片的输出地址的Uri对象作为值传入,以MediaStore.EXTRA_OUTPUT 作为键(与多媒体相关的可以从MediaStore这个类中得到)

指定照片存放路径

拍照需要指定图片存储路径,在上述代码中将Uri对象作为图片输出路径,通过以下代码来构建这个Uri对象:

private Uri imageUri;
// 创建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, "cn.chenjianlink.cameraalbumtest.fileprovider", outputImage);
} else {
	imageUri = Uri.fromFile(outputImage);
}
  • 照片属于文件,用File类临时保存。

  • getExternalCacheDir() 方法用于获取应用程序关联目录,具体的路径是:/sdcard/Android/data/<package name>/cache(这个目录是SD卡中专门用于存放当前应用缓存数据的位置,由于从Android 6.0系统开始,读写SD卡被列为了危险权限,如果将图片存放在SD卡的任何其他目录,都要进行运行时权限处理才行,而使用应用关联目录则可以跳过这一步)

  • Build.VERSION.SDK_INT用于获取当前系统的SDK版本

  • 如果运行设备的系统版本低于Android 7.0,就调用Uri的**fromFile()**方法将File对象转换成Uri对象,这个Uri对象标识着output_image.jpg这张图片的本地真实路径。

  • 从Android 7.0系统开始,直接使用本地真实路径的Uri被认为是不安全的,会抛出一个FileUriExposedException异常。而FileProvider则是一种特殊的内容提供器,它使用了和内容提供器类似的机制来对数据进行保护,可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。
    在系统版本高于Android 7.0的设备上,调用FileProvider的getUriForFile() 方法将File 对象转换成一个封装过的Uri对象,这个方法有三个参数:

public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file)
  • Context context:Context对象,一般传入调用的程序当前Activity
  • String authority:任意唯一的字符串,作为唯一标识
  • File file:将要转换的文件的file对象传入
  • 使用FileProvider,需要在AndroidManifest.xml中对这个内容提供器进行注册
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.chenjianlink.cameraalbumtest">
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
	<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="cn.chenjianlink.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>
  • 访问SD卡需要声明权限(Android 4.4系统以前)
  • android:authorities属性的值必须要和FileProvider.getUriForFile() 方法中的第二个参数一致
  • <provider>标签中使用<meta-data>来指定Uri的共享路径,引用@xml/file_paths 资源,这个文件内容如下:
<?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卡共享)

照片显示

拍完照之后,一般需要回到应用程序中,则需要重写Activity类中的onActivityResult() 方法,在这个方法中执行相关逻辑。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	//自定义逻辑
}

如果需要将拍摄好的照片在应用程序中显示,则需要使用调用BitmapFactory的
decodeStream() 方法将照片解析成Bitmap 对象,然后把它设置到ImageView中显示出来:

// 将拍摄的照片显示出来
private ImageView picture = (ImageView) findViewById(R.id.picture);
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);

还需要在布局文件中引入ImageView这个控件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="match_parent"
	android:layout_height="match_parent" >
	···
	<ImageView
		android:id="@+id/picture"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_gravity="center_horizontal" />
	
	···
</LinearLayout>

从相册中选取照片

应用程序调用照片还可从手机相册里

打开相册

用Intent对象打开相册,相关代码如下:

Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent, 1); // 打开相册

打开相册需要动态申请WRITE_EXTERNAL_STORAGE 这个危险权限,相册中的照片都是存储在SD卡上的,要从SD卡中读取照片就需要申请这个权限。WRITE_EXTERNAL_STORAGE 表示同时授予程序对SD卡读和写的能力。

相册图片显示

Android系统从4.4版本开始,选取相册中的图片不再返回图片真实的Uri了,而是一个封装过的Uri,因此如果是4.4版本以上的手机就需要对这个Uri进行解析才能获取照片的真实路径。

在Android 4.4以下系统获取图片真实路径:

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 {
		//···
	}
}

在Android 4.4以上系统获取图片真实路径:

@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); // 根据图片路径显示图片
}
  • 如果返回的Uri是document类型的话,那就取出document id 进行处理,如果不是的话,那就使用普通的方式处理。
  • 如果Uri的authority是media格式的话,document id还需要再进行一次解析,要通过字符串分割的方式取出后半部分才能得到真正的数字id。

播放多媒体文件

手机上最常见的休闲方式毫无疑问就是听音乐和看电影了,Android在播放音频和视频方面也是做了相当不错的支持,它提供了一套较为完整的API,使得开发者可以很轻松地编写出一个简易的音频或视频播放器。

播放音频

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

MediaPlayer常用方法如下:
Android学习羁绊之多媒体开发

MediaPlayer的工作流程如下:

  1. 创建出一个MediaPlayer对象
  2. 调用 setDataSource() 方法来设置音频文件的路径
  3. 调用prepare() 方法使MediaPlayer进入到准备状态
  4. 调用start() 方法开始b播放
  5. 播放音频,调用pause() 方法就会暂停播放,调用reset() 方法就会停止播放

在不再使用MediaPlayer时,分别调用stop() 方法和release() 方法,将与MediaPlayer相关的资源释放掉

播放视频

播放视频文件使用VideoView类来实现,这个类将视频的显示和控制集于一身。

VideoView的用法和MediaPlayer也比较类似,主要有以下常用方法:
Android学习羁绊之多媒体开发

VideoView的工作流程如下:

  1. 创建出一个VideoView对象
  2. 调用setVideoPath() 方法来设置音频文件的路径
  3. 调用start() 方法开始播放
  4. 播放视频,调用pause() 方法就会暂停播放

要在程序中显示播放的视频,需要在布局文件中添加VideoView这个控件。