天天看点

第一行代码学习笔记第八章——运用手机多媒体

知识点目录

  • 8.1 将程序运行到手机上
  • 8.2 使用通知

    * 8.2.1 通知的基本使用

    * 8.2.2 通知的进阶技巧

    * 8.2.3 通知的高级功能

  • 8.3 调用摄像头和相册

    * 8.3.1 调用摄像头拍照

    * 8.3.2 从相册中选择照片

  • 8.4 播放多媒体文件

    * 8.4.1 播放音频

    * 8.4.2 播放视频

  • 8.5 小结与点评

知识点回顾

8.1 将程序运行到手机上

前面的章节我们都是使用模拟器来运行程序的,但这章的Demo都需要在真正的Android手机上运行。

打开开发者选项:

从Android4.2系统开始,开发者选项是默认隐藏的,需要先进入到“关于手机”界面,然后对连续点击版本号,直到让开发者选项显示出来。

手机连接电脑

打开开发者选项后,我们还需要通过数据线把手机连接到电脑上。然后进入到设置—>开发者选项界面,并在这个界面中勾选中USB调试选项。

如果是Windows操作系统,还需要在电脑上安装手机驱动。一般借助360手机助手或豌豆荚等工具进行快速安装,安装完成后就可以看到手机已经连接到电脑上了:

第一行代码学习笔记第八章——运用手机多媒体

8.2 使用通知

通知的主要的作用是:当应用程序不是在前台运行时,向用户发出一些提示信息。

发出一条通知后,手机最上方的状态栏会显示一个通知的图标,下拉状态栏后可以看到通知的详细内容。

8.2.1 通知的基本使用

通知可以在活动、广播接收器或服务里创建。但无论在哪创建使用步骤都是一样的。

  1. 获取NotificationManager来对通知进行管理

    NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

  2. 创建Notification对象
    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))
             .build();
               

几乎每个Android版本都会对通知这部分进行修改,为了更好的兼容性,我们使用support库中提供的兼容API。support-v4库中提供了一个NotificationCompat类,使用这个类的构造器来创建Notification对象,就可以保证我们的程序在所有Android系统版本上都能正常工作。

  • setContentTitle()方法指定通知的标题内容
  • setContentText()方法指定通知的正文内容
  • setWhen()方法指定通知被创建的时间,以毫秒为单位,当下拉系统状态栏时,这里指定的时间会显示在相应的通知上
  • setSmallIcon()方法设置通知的小图标,只能使用纯alpha图层的图片进行设置,小图标会显示在系统状态栏上
  • setLargeIcon()方法设置通知的大图标,下拉系统状态栏时,就可以看到设置的大图标
  1. 调用NotificationManager的notify()方法
    manager.notify(1,notification);
               

    参数一:id,要保证为每个通知所指定的id都是不同的

    参数二:创建的Notification对象

示例代码:

  1. 定义一个notification触发点
    <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/send_notice"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textAllCaps="false"
             android:text="Send notice"/>
     
     </LinearLayout>
               
  2. 创建通知
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
     
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.activity_main);
     
             Button sendNotice = (Button) findViewById(R.id.send_notice);
             sendNotice.setOnClickListener(this);
         }
     
         @Override
         public void onClick(View v) {
             switch (v.getId()) {
                 case R.id.send_notice:
                     String id = "my_channel_01";
                     String name = "我是渠道名字";
                     NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                     Notification notification = null;
                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//Android 8.0以上
                         Toast.makeText(this, "Build.VERSION.SDK_INT = " + Build.VERSION.SDK_INT, Toast.LENGTH_SHORT).show();
                         NotificationChannel channel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_DEFAULT);
                         manager.createNotificationChannel(channel);
                         notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去
                                 .setContentTitle("This is content title")
                                 .setContentText("heheda")
                                 .setWhen(System.currentTimeMillis())
                                 .setSmallIcon(R.mipmap.ic_launcher)
                                 .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                                 .build();
                     } else {//Android8.0以下
                         Toast.makeText(this, "Build.VERSION.SDK_INT = " + Build.VERSION.SDK_INT, Toast.LENGTH_SHORT).show();
                         notification = new NotificationCompat.Builder(this)
                                 .setContentTitle("This is content title")
                                 .setContentText("hahaha")
                                 .setWhen(System.currentTimeMillis())
                                 .setSmallIcon(R.mipmap.ic_launcher)
                                 .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                                 .build();
                     }
                     manager.notify(1, notification);
                     break;
                 default:
                     break;
             }
         }
     }
               

备注:在Android8.0以上发送通知的稍有不同,需要NotificationChannel。

PendingIntent

PendingIntent跟Intent有许多相似之处,都可以指明一个"意图",都可以启动活动、启动服务以及发送广播。不同的是Intent更加倾向于去立即执行某个动作,而PendingIntent更加倾向于在某个合适的时机去执行某个动作。所以,可以把PendingIntent理解为延迟执行的Intent

PendingIntent的基本用法:

  1. 获取PendingIntent实例

根据具体的需求来选择使用getActivity()方法、getBroadcast()方法或getService()方法。

Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
           

getActivity()方法、getBroadcast()方法或getService()方法接收的参数都是相同的:

参数一:上下文

参数二:请求码,一般传入0即可

参数三:Intent对象

参数四:用于确定PendingIntent的行为。有FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT这4个值可选,通常传入0即可。

2.连缀在setContentIntent()方法中

notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去
        .setContentTitle("This is content title")
        .setContentText("heheda")
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.mipmap.ic_launcher)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
        .setContentIntent(pendingIntent) //连缀PendingIntent
        .build();
           

这样下拉系统状态栏,点击通知后,就会进入到NotificationActivity界面。

让状态栏上通知栏消失:

上面进入到NotificationActivity界面后,发现系统状态栏上的通知栏图标还没有消失。

让状态栏上通知栏消失的方法有两种:

  • 在NotificationCompat.Builder中再连缀一个
    notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去
              .setContentTitle("This is content title")
              .setContentText("heheda")
              .setWhen(System.currentTimeMillis())
              .setSmallIcon(R.mipmap.ic_launcher)
              .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
              .setContentIntent(pendingIntent)
              .setAutoCancel(true) //点击这个通知时,通知会自动取消掉
              .build();
               
  • 显示地调用NotificationManager的cancel()方法
    public class NotificationActivity extends AppCompatActivity {
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_notification);
              NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
              manager.cancel(1);//这里的1,就是创建通知的时候给每条通知指定的id
          }
      }
               
8.2.2 通知的进阶技巧

NotificationCompat.Builder中提供了非常丰富的API来让我们创建出更加多样的通知效果。下面我们学习一些常用的API。

setSound()

在通知发出的时候播放一段音频。setSound()方法接收一个Uri参数,所以在指定音频文件的时候还需要先获取到音频文件对应的URI。例如:每个手机的/system/media/audio/ringtones目录下很多音频文件,我们可以从中随便选一个音频文件,那么就可以在代码中这样指定;

Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去
        ....
        .setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg"))) //指定音频
        .build();
           

setVibrate()

用于设置手机静止和振动的时长,以毫秒为单位。下标为0的值表示手机静止的时长,下标为1的值表示手机振动的时长,下标为2的值又表示手机静止的时长,以此类推。

Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去
            ....
            .setVibrate(new long[]{0,1000,1000,1000}) //指定振动时长
            .build();
           

备注:控制手机振动需要声明权限:

<uses-permission android:name="android.permission.VIBRATE"/>
           

setLights()

用于控制在通知到来时,控制手机LED灯的显示。

Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去
            ....
            .setLights(Color.GREEN,1000,1000)
            .build();
           

参数一:指定LED灯的颜色

参数二:指定LED灯亮起的时长,以毫秒为单位

参数三:指定LED灯暗去的时长,以毫秒为单位

setDefaults()

如果不想繁杂的设置,可以直接使用通知的默认效果,它会根据当前手机的环境来决定播放什么铃声,以及如何振动。

Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去
            ....
            .setDefaults(NotificationCompat.DEFAULT_ALL) //使用默认效果
            .build();
           
8.2.3 通知的高级功能

NotificationCompat.Builder中还有更强大的API,可以构建出更加丰富的通知效果。

setStyle()

当通知栏中的内容足够长的时候,正常情况下是无法完全显示的,如下图所示:

第一行代码学习笔记第八章——运用手机多媒体

如果我们想让通知栏中的内容完全显示,可以使用setStyle()方法:

Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去
            ....
            .setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notification ,send and sync data,and use voice actions. Get the official" +
                                "Andoird IDE and developer tools to build apps for Android."))
            .build();
           

运行后的效果如下:

第一行代码学习笔记第八章——运用手机多媒体

除了显示长文字之外,通知里还可以显示一张大图片,具体用法如下:

Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去
            ....
            .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.drawable.big_image)))
            .build();
           

运行后效果如下:

第一行代码学习笔记第八章——运用手机多媒体

setPriority()

用于设置通知的重要程度。该方法接收一个整型参数用于设置这条通知的重要程度,共有5个指可选:

  • PRIORITY_DEFAULT 表示默认的重要程度
  • PRIORITY_MIN 表示最低的重要程度
  • PRIORITY_LOW 表示较低的重要程度
  • PRIORITY_HIGH 表示较高的重要程度
  • PRIORITY_MAX 表示最高的重要程度

具体用法如下:

Notification notification = new NotificationCompat.Builder(this, id)//这里要传一个id进去
            ....
            .setPriority(NotificationCompat.PRIORITY_MAX)
            .build();
           

8.3 调用摄像头和相册

调用系统的摄像头进行拍照或者调用系统中的相册都是很常见的功能需求。

8.3.1 调用摄像头拍照

调用系统摄像头拍照的步骤一般如下:

第一步:定义一个触发点

<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/take_photo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Take Photo"
        android:textAllCaps="false"/>

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

</LinearLayout>
           

布局中一个Button和一个ImageView。Button用于打开摄像头进行拍照,ImageView用于将拍到的图片显示出来。

第二步:调用摄像头的具体逻辑

public class MainActivity extends AppCompatActivity {

    private static final int TAKE_PHOTO = 1;
    private Button mTakePhoto;
    private Button mMTakePhoto;
    private ImageView mPicture;
    private Uri mImageUri;

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

        mMTakePhoto = (Button) findViewById(R.id.take_photo);
        mPicture = (ImageView) findViewById(R.id.picture);

        mMTakePhoto.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();
                }

                //将创建的File对象转换为Uri对象
                if (Build.VERSION.SDK_INT >= 24) {
                    mImageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.cameraalbumtest.fileprovider", outputImage);
                } else {
                    mImageUri = Uri.fromFile(outputImage);
                }

                //启动相机程序
                Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
                intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
                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(mImageUri));
                        mPicture.setImageBitmap(bitmap);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
        }
    }
}
           

对上面的代码分四大块进行解释:

1. 创建存放拍下图片的路径

创建一个File对象,用于存放摄像头拍下的图片,并命名为output_image.jpg,并将它存放在手机SD卡的应用关联缓存目录下。

应用关联缓存目录是指SD卡中专门用于存放当前应用缓存数据的位置,调用getExternalCacheDir()方法就可以得到这个目录,具体路径是/sdcard/Android/data/com.example.cameraalbumtest/cache ,如下图所示:

第一行代码学习笔记第八章——运用手机多媒体

为什么要是使用应用关联缓存目录呢?因为从Android6.0开始,读写SD卡被列为危险权限,如果将图片存放在SD卡的任何其他目录,都需要进行运行是权限处理才行,而使用应用关联目录则可以逃过这一步。

2. 将创建的File对象转换为Uri对象

在低于Android7.0时,就调用Uri的fromFile()方法将File对象转换为Uri对象。

在高于Android7.0时,就调用FileProvider的getUriForFile()方法将File对象转换成一个封装过的Uri对象。该方法共接收三个参数:

参数一:Context对象

参数二:authority字符串。需要跟后面再AndroidManifest.xml注册时保持一致

参数三:创建的File对象

之所以要进行这样一层转换,因为从Android7.0开始,直接使用本地真实路径的Uri被认为是不安全的,会抛出一个FileUriExposedException异常,而FileProvider则是一种特殊的内容提供器,它使用了和内容提供器类似的机制来对数据进行了保护,可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。

这个Uri对象标识着output_image.jpg这张图片的本地真实路径。

3. 启动相机程序

构建一个Intent对象,并将Intent的action指定为android.media.action.IMAGE_CAPTURE,再调用putExtra()方法指定图片的输出地址,最后调用startActivityForResult()来启动活动。

这样我是使用的隐式Intent,系统会找到能够响应这个Intent的活动去启动,这样照相机程序就会被打开,拍下的照片将会输出到output_image.jpg中

4. 将照片显示出来

因为上面用的是startActivityForResult()方法,当照相机程序拍照完成后,就会返回到onActivityResult()方法中,如果发现拍照成功,就可以调用BitmapFactory的decodeStream()方法将output_image.jpg这张照片解析成Bitmap对象,然后把它设置到ImageView中显示出来。

第三步:注册内容提供器

上面用到了FileProvider,所以就需要到AndroidManifest.xml中去注册:

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <provider
        android:authorities="com.example.cameraalbumtest.fileprovider"
        android:name="android.support.v4.content.FileProvider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths"/>
    </provider>
</application>
           

属性解释:

  • android:authorities 指定authorities,需要与FileProvider.getUriForFile()方法中的第二个参数一致
  • android:name 固定值。必须是android.support.v4.content.FileProvider
  • android:exported 表示是否允许外部程序访问我们的内容提供器
  • android:grantUriPermissions 用于给内容提供器的数据子集授权。true表示权限能够被授予内容提供器范围内的任何数据;false表示权限只能授予这个元素所指定的数据子集。
  • 指定Uri的共享路径,并应用一个@xml/file_paths资源

第四步:创建file_paths资源

右击res目录—>New—>Directory,创建一个xml目录,接着右击xml目录—>New—>File,创建一个file_paths.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这张图片的路径。

第五步:添加权限

在Android4.4之前,访问SD卡的应用关联目录也是要权限的,Android4.4之后的系统则不再需要权限声明了,为了能够兼容老版本,在AndroidManifest.xml中加入相应权限。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
           

效果图:

将程序运行到手机上,点击Take Photo按钮进行拍照,拍照完成后,点击√,回到我们程序界面,拍照的照片就会显示出来。如下图所示:

第一行代码学习笔记第八章——运用手机多媒体
8.3.2 从相册中选择照片

从相册中选择已经拍摄好的照片也是非常常见的功能。

实现从相册中选择照片的功能一般步骤如下:

第一步:新增一个触发点

在前面CameraAlbumTest的基础上,添加一个Button:

<Button
    android:id="@+id/choose_from_album"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textAllCaps="false"
    android:text="Choose From Album"/>
           

Button用于打开系统相册,然后再在ImageView上将选中的图片显示出来。

第二步:打开相册的具体逻辑

public class MainActivity extends AppCompatActivity {

    private static final int TAKE_PHOTO = 1;
    private static final int CHOOSE_PHOTO = 2;
    private Button mMTakePhoto;
    private ImageView mPicture;
    private Uri mImageUri;

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

        mMTakePhoto = (Button) findViewById(R.id.take_photo);
        mPicture = (ImageView) findViewById(R.id.picture);

        ......

        //从相册中选择照片
        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, @NonNull String[] permissions, @NonNull 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;
        }
    }

    @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) {
                        handleImageOnKitKat(data);
                    } else {
                        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 cententUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
                imagePath = getImagePath(cententUri, 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;
        Cursor cursor = null;
        //通过Uri和selection来获取真实的图片路径
        try {
            cursor = getContentResolver().query(uri, null, selection, null, null);
            if (cursor != null) {
                if (cursor.moveToFirst()) {
                    path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return path;
    }

	//将照片显示出来
    private void displayImage(String imagePath) {
        if (imagePath != null) {
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            mPicture.setImageBitmap(bitmap);
        } else {
            Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show();
        }
    }
}
           

解释说明:

  • 需要动态申请WRITE_EXTERNAL_STORAGE这个危险权限
  • 从Android4.4开始,选取相册中的图片不再返回图片真实的Uri了,而是一个封装过的Uri,所以在4.4版本以上的手机需要对这个Uri进行解析。返回的Uri分为document、content和file三种类型,需要从这三种情况去分别判断解析。

效果图:

重新运行程序,点击Choose From Album按钮,首先会弹出权限申请框,点击允许后就会打开手机相册,然后随意选择一张照片,回到我们程序的界面,选中的照片就会显示出来,如下图所示:

第一行代码学习笔记第八章——运用手机多媒体

调用摄像头拍照和从相册中选择照片的代码我已上传到我GitHub,有需要的朋友可以进入查看:

调用摄像头拍照和从相册中选择照片功能实现

8.4 播放多媒体文件

Android在播放音频和视频方面提供了一套较为完整的API,使得开发者可以很轻松地编写出一个简易的音频或视频播放器。

8.4.1 播放音频

Android中播放音频文件一般都是使用MediaPlayer类来实现。

MediaPlayer中常用的控制方法如下:

第一行代码学习笔记第八章——运用手机多媒体

下面我们在项目中实践下:

第一步:编写布局控件

<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/play"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Play"
        android:textAllCaps="false"/>

    <Button
        android:id="@+id/pause"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Pause"
        android:textAllCaps="false"/>

    <Button
        android:id="@+id/stop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Stop"
        android:textAllCaps="false"/>

</LinearLayout>
           

布局中放置3个按钮,分别用于对音频文件的播放、暂停和停止操作。

第二步:音频功能的具体实现

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "MainActivity";
    private MediaPlayer mMediaPlayer = 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(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
        } else {
            initMediaPlayer();//初始化MediaPlayer
        }
    }

    private void initMediaPlayer() {
        try {
            File file = new File(Environment.getExternalStorageDirectory(),"audio.wma");
            mMediaPlayer.setDataSource(file.getPath());//指定音频文件的路径
            mMediaPlayer.prepare();//让MediaPlayer进入到准备状态
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull 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:
                break;
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.play:
                if (!mMediaPlayer.isPlaying()) {
                    mMediaPlayer.start();//开始播放
                }
                break;
            case R.id.pause:
                if (mMediaPlayer.isPlaying()) {
                    mMediaPlayer.pause();//暂停播放
                }
                break;
            case R.id.stop:
                if (mMediaPlayer.isPlaying()) {
                    mMediaPlayer.stop();//停止播放
                }
                break;
            default:
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.release();
        }
    }
}
           

第三步:加入权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
           

效果图:

运行程序后,会先申请权限,同意授权后,就会出现如下界面:

第一行代码学习笔记第八章——运用手机多媒体

点击Play按钮就会播放音频。

点击Pause按钮,停止播放音频,再次点击Play按钮,会接着暂停之前的位置继续播放。

点击Stop按钮,停止播放音频,但再次点击Play按钮时,音乐会从从头开始播放。

8.4.2 播放视频

Android中播放视频文件一般都是使用VideoView类来实现。

VideoView中常用的控制方法如下:

第一行代码学习笔记第八章——运用手机多媒体

下面我们在项目中实践下:

第一步:编写布局控件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Button
            android:id="@+id/play"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textAllCaps="false"
            android:text="Play"/>

        <Button
            android:id="@+id/pause"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textAllCaps="false"
            android:text="Pause"/>

        <Button
            android:id="@+id/replay"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:textAllCaps="false"
            android:text="Replay"/>

    </LinearLayout>

    <VideoView
        android:id="@+id/video_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
           

放置3个按钮,分别用于控制视频的播放、暂停和重新播放。下面放置一个VideoView,用于显示要播放的视频。

第二步:视频功能的具体实现

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "MainActivity";

    private VideoView mVideoView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mVideoView = (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(), "ad.mp4");
        mVideoView.setVideoPath(file.getPath());
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.play:
                if (!mVideoView.isPlaying()) {
                    mVideoView.start(); //开始播放
                }
                break;
            case R.id.pause:
                if (mVideoView.isPlaying()) {
                    mVideoView.pause(); //暂停播放
                }
                break;
            case R.id.replay:
                if (mVideoView.isPlaying()) {
                    mVideoView.resume(); //重新播放
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull 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;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mVideoView != null) {
            mVideoView.suspend(); //将VideoView所占的资源释放掉
        }
    }
}
           

第三步:加入权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
           

效果图:

运行程序后,会先申请权限,同意授权后,点击Play按钮,就可以看到视频已经开始播放了,如下图所示:

第一行代码学习笔记第八章——运用手机多媒体

点击Pause按钮可以暂停视频的播放,点击Replay按钮可以从头开始播放视频。

VideoView只是帮我们做了一个很好的封装,它的背后仍然使用MediaPlayer来对视频文件进行控制。

VideoView并不是一个万能的视频播放工具类,它在视频格式的支持以及播放效率方面都存在较大的不足。所以,如果想仅仅用VideoView编写出一个功能非常强大的视频播放器是不太现实的。比较适合于用于播放一些游戏类的片头动画或某个应用的宣传视频。

8.5 小结与点评

本章主要对Android系统的各种多媒体技术进行了学习,主要有:

  • 通知的使用技巧
  • 调用摄像头拍照
  • 从相册中选取照片
  • 播放音频文件
  • 播放视频文件
非常感谢您的耐心阅读,希望我的文章对您有帮助。欢迎点评、转发或分享给您的朋友或技术群。

继续阅读