【Android】多媒体使用

多媒体使用

调用摄像头和相册

调用摄像头拍照

xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button android:id="@+id/takePhotoBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Take Photo" />

    <ImageView android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

</LinearLayout>

main

package com.example.cameraalbumtest;

// 导入必要的Android SDK类和包
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import androidx.exifinterface.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.Button;
import android.widget.ImageView;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {
    // 请求代码,用于标识拍照的请求
    private static final int TAKE_PHOTO = 1;
    
    // Uri用于存储拍照后图片的位置
    private Uri imageUri;
    // File对象用于存储拍照后图片的实际路径
    private File outputImage;
    // ImageView控件用于显示图片
    private ImageView imageView;

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

        // 初始化imageView控件
        imageView = findViewById(R.id.imageView);

        // 初始化拍照按钮,并设置点击事件
        Button takePhotoBtn = findViewById(R.id.takePhotoBtn);
        takePhotoBtn.setOnClickListener(view -> {
            // 创建File对象,用于存储拍照后的图片
            outputImage = new File(getExternalCacheDir(), "output_image.jpg");
            if (outputImage.exists()) {
                outputImage.delete(); // 如果文件已存在,则删除
            }
            try {
                outputImage.createNewFile(); // 创建新文件
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 获取imageUri,用于存储图片的URI
            imageUri = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
                    ? FileProvider.getUriForFile(this, "com.example.cameraalbumtest.fileprovider", outputImage)
                    : Uri.fromFile(outputImage);

            // 启动相机程序
            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
            startActivityForResult(intent, TAKE_PHOTO); // 启动相机并请求拍照
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // 判断是否是拍照请求并且请求成功
        if (requestCode == TAKE_PHOTO && resultCode == Activity.RESULT_OK) {
            // 将拍摄的照片显示出来
            Bitmap bitmap = null;
            try {
                bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }
            // 根据EXIF信息旋转图片,并设置到ImageView
            imageView.setImageBitmap(rotateIfRequired(bitmap));
        }
    }

    // 根据EXIF信息旋转图片
    private Bitmap rotateIfRequired(Bitmap bitmap) {
        ExifInterface exif = null;
        try {
            exif = new ExifInterface(outputImage.getPath()); // 获取ExifInterface对象
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 获取图片的方向信息
        int orientation = exif == null ? ExifInterface.ORIENTATION_NORMAL : exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
        // 根据方向信息旋转图片
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                return rotateBitmap(bitmap, 90);
            case ExifInterface.ORIENTATION_ROTATE_180:
                return rotateBitmap(bitmap, 180);
            case ExifInterface.ORIENTATION_ROTATE_270:
                return rotateBitmap(bitmap, 270);
            default:
                return bitmap;
        }
    }

    // 旋转图片的方法
    private Bitmap rotateBitmap(Bitmap bitmap, int degree) {
        Matrix matrix = new Matrix();
        matrix.postRotate(degree); // 设置旋转角度
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    }
}

首先这里创建了一个File对象,用于存放摄像头拍下的图片,这里我们把图片命名为output_image.jpg,并存放在手机SD卡的应用关联缓存目录下。什么叫作应用关联缓存目录呢?就是指SD卡中专门用于存放当前应用缓存数据的位置,调用getExternalCacheDir()方法可以得到这个目录,具体的路径是/sdcard/Android/data//cache。那么为什么要使用应用关联缓存目录来存放图片呢?因为从Android 6.0系统开始,读写SD卡被列为了危险权限,如果将图片存放在SD卡的任何其他目录,都要进行运行时权限处理才行,而使用应用关联目录则可以跳过这一步。另外,从Android 10.0系统开始,公有的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则是一种特殊的ContentProvider,它使用了和ContentProvider类似的机制来对数据进行保护,可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。

接下来构建了一个Intent对象,并将这个Intent的action指定为android.media.action.IMAGE_CAPTURE,再调用Intent的putExtra()方法指定图片的输出地址,这里填入刚刚得到的Uri对象,最后调用startActivityForResult()启动Activity。由于我们使用的是一个隐式Intent,系统会找出能够响应这个Intent的Activity去启动,这样照相机程序就会被打开,拍下的照片将会输出到output_image.jpg中

由于刚才我们是使用startActivityForResult()启动Activity的,因此拍完照后会有结果返回到onActivityResult()方法中。如果发现拍照成功,就可以调用BitmapFactory的decodeStream()方法将output_image.jpg这张照片解析成Bitmap对象,然后把它设置到ImageView中显示出来。需要注意的是,调用照相机程序去拍照有可能会在一些手机上发生照片旋转的情况。这是因为这些手机认为打开摄像头进行拍摄时手机就应该是横屏的,因此回到竖屏的情况下就会发生90度的旋转。为此,这里我们又加上了判断图片方向的代码,如果发现图片需要进行旋转,那么就先将图片旋转相应的角度,然后再显示到界面上。

才提到了ContentProvider,那么我们自然要在AndroidManifest.xml中对它进行注册才行,代码如下所示:

<provider android:name="androidx.core.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>

android:name属性的值是固定的,而android:authorities属性的值必须和刚才FileProvider.getUriForFile()方法中的第二个参数一致。另外,这里还在标签的内部使用指定Uri的共享路径,并引用了一个@xml/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这张图片的路径。

FileProvider

了解 FileProvider 的工作原理对于理解 Android 中的权限模型和 Uri 处理非常重要,尤其是在处理文件 URI 和系统权限时。以下是 FileProvider 的一些关键点:

  1. 权限隔离:在 Android 6.0(API 级别 23)及更高版本中,引入了运行时权限的概念。FileProvider 允许您安全地共享文件给其他应用,同时保持应用间的权限隔离。

  2. 内容 URIFileProvider 将文件的文件系统路径转换为一个 content 类型的 Uricontent URI 是一种统一资源标识符,用于通过内容提供者(ContentProvider)访问数据。

  3. 使用方式:当您需要访问存储在设备上的文件时,尤其是当这些文件位于应用的专用目录之外时,您可以使用 FileProvider 来获取这些文件的 content URI。

  4. 配置:要使用 FileProvider,您需要在应用的 AndroidManifest.xml 文件中声明它,并在 res/xml 目录下添加一个配置文件,通常名为 file_paths.xml,来指定可以共享的文件路径。

  5. 安全性FileProvider 允许您为每个文件设置临时访问权限。当您通过 Intent 分享文件时,系统会自动处理权限授予,确保接收方只能临时访问该文件。

  6. 示例:在您提供的代码中,FileProvider 用于获取输出图片文件的 Uri,然后将其传递给相机 Intent。这样,即使在 Android N 及更高版本中,应用也能够安全地访问存储在外部存储上的文件。

  7. 注意:使用 FileProvider 时,您需要确保 AndroidManifest.xml 中的授权配置正确,并且 file_paths.xml 中定义的路径与您尝试访问的文件路径匹配。

这是一个使用 FileProvider 的基本示例:

<!-- AndroidManifest.xml -->
<provider
    android:name="androidx.core.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>
<!-- res/xml/file_paths.xml -->
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <cache-path name="my_cache" path="."/>
</paths>

在这个示例中,FileProvider 被配置为可以访问应用的缓存目录。通过这种方式,您可以安全地将文件 URI 传递给其他应用,同时控制访问权限。

从相册中选择图片

虽然调用摄像头拍照既方便又快捷,但我们并不是每次都需要当场拍一张照片的。因为每个人的手机相册里应该都会存有许多张图片,直接从相册里选取一张现有的图片会比打开相机拍一张照片更加常用。一个优秀的应用程序应该将这两种选择方式都提供给用户,由用户来决定使用哪一种。下面我们就来看一下,如何才能实现从相册中选择图片的功能。

还是在CameraAlbumTest项目的基础上进行修改,编辑activity_main.xml文件,在布局中添加一个按钮,用于从相册中选择图片,

然后修改MainActivity中的代码,加入从相册选择图片的逻辑,代码如下所示:

播放多媒体文件

播放音乐

在这里插入图片描述

简单了解了上述方法后,我们再来梳理一下MediaPlayer的工作流程。首先需要创建一个MediaPlayer对象,然后调用setDataSource()方法设置音频文件的路径,再调用prepare()方法使MediaPlayer进入准备状态,接下来调用start()方法就可以开始播放音频,调用pause()方法就会暂停播放,调用reset()方法就会停止播放。

布局文件中放置了3个按钮,分别用于对音频文件进行播放、暂停和停止操作。MediaPlayer可以用于播放网络、本地以及应用程序安装包中的音频。这里简单起见,我们就以播放应用程序安装包中的音频来举例吧。Android Studio允许我们在项目工程中创建一个assets目录,并在这个目录下存放任意文件和子目录,这些文件和子目录在项目打包时会一并被打包到安装文件中,然后我们在程序中就可以借助AssetManager这个类提供的接口对assets目录下的文件进行读取。

那么首先来创建assets目录吧,它必须创建在app/src/main这个目录下面,也就是和java、res这两个目录是平级的。右击app/src/main→New→Directory,在弹出的对话框中输入“assets”,目录就创建完成了。由于我们要播放音频文件,这里我提前准备好了一份music.mp3资源(资源下载方式见前言),将它放入assets目录中即可

在类初始化的时候,我们就先创建了一个MediaPlayer的实例,然后在onCreate()方法中调用initMediaPlayer()方法,为MediaPlayer对象进行初始化操作。

在initMediaPlayer()方法中,首先通过getAssets()方法得到了一个AssetManager的实例,AssetManager可用于读取assets目录下的任何资源。

接着我们调用了openFd()方法将音频文件句柄打开,后面又依次调用了setDataSource()方法和prepare()方法,为MediaPlayer做好了播放前的准备。

接下来我们看一下各个按钮的点击事件中的代码。当点击“Play”按钮时会进行判断,如果当前MediaPlayer没有正在播放音频,则调用start()方法开始播放。

当点击“Pause”按钮时会判断,如果当前MediaPlayer正在播放音频,则调用pause()方法暂停播放。

当点击“Stop”按钮时会判断,如果当前MediaPlayer正在播放音频,则调用reset()方法将MediaPlayer重置为刚刚创建的状态,然后重新调用一遍initMediaPlayer()方法。

最后在onDestroy()方法中,我们还需要分别调用stop()方法和release()方法,将与MediaPlayer相关的资源释放掉。这样一个简易版的音乐播放器就完成了,现在将程序运行到手机上

播放视频

在这里插入图片描述

这个布局文件中同样放置了3个按钮,分别用于控制视频的播放、暂停和重新播放。另外在按钮的下面又放置了一个VideoView,稍后的视频就将在这里显示。接下来的问题就是存放视频资源了,很可惜的是,VideoView不支持直接播放assets目录下的视频资源,所以我们只能寻找其他的解决方案。

res目录下允许我们再创建一个raw目录,像诸如音频、视频之类的资源文件也可以放在这里,并且VideoView是可以直接播放这个目录下的视频资源的。

现在右击app/src/main/res→New→Directory,在弹出的对话框中输入“raw”,完成raw目录的创建,并把要播放的视频资源放在里面。

我们首先在onCreate()方法中调用了Uri.parse()方法,将raw目录下的video.mp4文件解析成了一个Uri对象,这里使用的写法是Android要求的固定写法。

然后调用VideoView的setVideoURI()方法将刚才解析出来的Uri对象传入,这样VideoView就初始化完成了。

下面看一下各个按钮的点击事件。

当点击“Play”按钮时会判断,如果当前没有正在播放视频,则调用start()方法开始播放。

当点击“Pause”按钮时会判断,如果当前视频正在播放,则调用pause()方法暂停播放。

当点击“Replay”按钮时会判断,如果当前视频正在播放,则调用resume()方法从头播放视频。

最后在onDestroy()方法中,我们还需要调用一下suspend()方法,将VideoView所占用的资源释放掉。现在将程序运行到手机上,点击一下“Play”按钮,就可以看到视频已经开始播放

其实VideoView只是帮我们做了一个很好的封装而已,它的背后仍然是使用MediaPlayer对视频文件进行控制的。另外需要注意,VideoView并不是一个万能的视频播放工具类,它在视频格式的支持以及播放效率方面都存在着较大的不足。所以,如果想要仅仅使用VideoView就编写出一个功能非常强大的视频播放器是不太现实的。但是如果只是用于播放一些游戏的片头动画,或者某个应用的视频宣传,使用VideoView还是绰绰有余的。

视频作为背景

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentBottom="true"
        android:layout_alignParentTop="true"
        android:layout_gravity="center" />
</RelativeLayout>

新建一个xml文件如上

在要作为背景的地方<include layout="@layout/videoback" />使用这句

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值