第一行代码(八)

第八章主要讲了手机中的多媒体技术

一、通知

通知的基本用法:

  通知(Notification)是 Android 系统一个比较有特色的功能,当某个应用程序希望向用户发出一些提示信息,而该程序又不在前台运行时,就可以借助通知来实现。
  通知既可以在活动中创建,也可以在广播接收器里创建,还可以在服务中创建。

        /*
            首先获取一个 NotificationManager 来对通知进行管理
         */
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        /*
            这里请使用 Support 包下的 NotificationCompat.Builder()来创建一个 Notification 对象,
            因为几乎 Android 系统每一个版本都会对通知这部分功能进行修改,API 很不稳定,所以使用兼容包
            下的 NotificationCompat 来兼容。
         */
        Notification notification = new NotificationCompat.Builder(SecondActivity.this)
                .setContentTitle("this is title")//设置通知的标题内容
                .setContentText("this is text")//设置通知的正文内容
                .setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
                .setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
                .build();
        /*
            让通知显示出来
            参1:id,保证每个通知所指定的 id 都是唯一的
            参2:是 Notification 对象
         */
        manager.notify(1, notification);
第一行代码(八)_第1张图片
image.png

注意:这个时候如果点击这条通知,还没有任何效果,如果要有相应的点击效果,比如跳转界面,就需要使用 PendingIntent 。PendingIntent和Intent的区别是:Intent 更倾向于立即去执行某个动作,而 PendingIntent 更倾向于在某个合适的时机去执行某个动作。

  pendingIntent 用法很简单,主要提供了几个静态的方法用于获取 PendingIntent,根据需求来选择使用 getActivity()方法、getBroadcast()方法、getService()方法

Intent intent = new Intent(SecondActivity.this,MainActivity.class);
/*
   参1:是 Context
   参2:一般用不到,通常都是传入0
   参3:是一个 Intent 对象
   参4:确定 PendingIntent 的行为,
            1.FLAG_ONE_SHOT:相同的 PendingIntent 只能使用一次,且遇到相同的PendingIntent时
                            不会去更新 PendingIntent 中封装的意图的额外部分内容
            2.FLAG_NO_CREATE:如果创建的 PendingIntent 尚未存在,则不创建新的 PendingIntent,
                             直接返回空
            3.FLAG_CANCEL_CURRENT:如果要创建的 PendingIntent 已存在,那么在创建
                             新的PendingIntent之前,原先已存在的 PendingIntent
                             中的意图将不能使用
            4.FLAG_UPDATE_CURRENT:如果要创建的 PendingIntent 已经存在,保留原先的 PendingIntent
                             的同时,将原先 PendingIntent 中封装的意图中的多余部分替换为现在
                             新创建的的PendingIntent的意向中额外的内容
            5,通常传入0即可:不打算使用任何一种标志来控制PendingIntent 的创建
                 */
PendingIntent pendingIntent = PendingIntent.getActivity(SecondActivity.this,0,intent,0);

  那么,创建通知的代码就可以更改为

        Intent intent = new Intent(this,NotificationActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
        /*
            首先获取一个 NotificationManager 来对通知进行管理
         */
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        /*
            这里请使用 Support 包下的 NotificationCompat.Builder()来创建一个 Notification 对象,
            因为几乎 Android 系统每一个版本都会对通知这部分功能进行修改,API 很不稳定,所以使用兼容包
            下的 NotificationCompat 来兼容。
         */
        Notification notification = new NotificationCompat.Builder(SecondActivity.this)
                .setContentTitle("this is title")//设置通知的标题内容
                .setContentText("this is text")//设置通知的正文内容
                .setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
                .setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
                .setContentIntent(pendingIntent)//用户点击通知要执行的意图
                .setAutoCancel(true)//点击通知后,通知会自动消失
                .build();
        /*
            通知自动消失也可以这样写,这个1,就是我们调用
            notify()方法时候传入的第一个参数,也就是通知的唯一标示:id,
            你想要取消哪条通知,就传入通知的 id 即可
         */
//        manager.cancel(1);
        /*
            让通知显示出来
            参1:id,保证每个通知所指定的 id 都是唯一的
            参2:是 Notification 对象
         */
        manager.notify(1, notification);

通知的进阶技巧:

这里说几个比较常见的 API:

  • setSound():在通知发出的时候播放一段音频,在指定音频文件的时候需要先获取音频文件对应的 URI
  • setVibrate():在通知到来的时候让手机进行震动。参数是一个长整型数组,用于设置手机静止和震动的时长,以毫秒为单位。下标为0或偶数表示手机静止的时长,下标为奇数表示手机震动的时长。不过记得需要申请权限
  • setLights():设置手机的 LED 灯,该方法接受三个参数,参1指定 LED 灯的颜色,参2指定灯亮起的时长,参3表示灯暗去的时长,都是以毫秒为单位。
  • setDefaults(NotificationCompat.DEFAULT_ALL):该方法用于设置通知的默认效果,它会根据当前的手机环境来决定播放什么铃声,如何震动等。
      Notification notification = new NotificationCompat.Builder(SecondActivity.this)
                .setContentTitle("this is title")//设置通知的标题内容
                .setContentText("this is text")//设置通知的正文内容
                .setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
                .setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
                .setContentIntent(pendingIntent)//用户点击通知要执行的意图
                .setAutoCancel(true)//点击通知后,通知会自动消失
                .setSound(Uri.fromFile(new File("/System/media/audio/ringtones/Luna.ogg")))//设置声音
                // 震动不要忘了权限:
                .setVibrate(new long[]{0,1000,1000,1000})//设置震动,
                .setLights(Color.GREEN,1000,1000)//设置 LED 灯
                //.setDefaults(NotificationCompat.DEFAULT_ALL)//默认效果
                .build();

通知的高级功能

  通知还有许多高级功能:

  • setStyle():该方法允许我们构建出富文本的通知内容,也就是说,通知中不光可以有文字和图标,还可以包含更多的东西,setStyle()方法接收一个 NotificationCompat.Style 参数,这个参数就是从来构建具体的富文本信息的,如长文字、图片等。

如果我们使用 setContentText()方法设置比较长的文字,通知内容是无法显示完整的,多余的部分会用省略号来代替,如果要显示长文字,需要使用 setStyle()方法。

      Notification notification = new NotificationCompat.Builder(SecondActivity.this)
                .setContentTitle("this is title")//设置通知的标题内容
                .setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
                .setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
                .setContentIntent(pendingIntent)//用户点击通知要执行的意图
                .setAutoCancel(true)//点击通知后,通知会自动消失
                .setDefaults(NotificationCompat.DEFAULT_ALL)//默认效果
                /*
                    通过 setStyle 方法,即使通知内容过长,也能全部展示出来
                 */
                .setStyle(new NotificationCompat.BigTextStyle().bigText("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"))
                .build();
  • setStyle():除了显示长文字外,还可以展示一张大图
        Notification notification = new NotificationCompat.Builder(SecondActivity.this)
                .setContentTitle("this is title")//设置通知的标题内容
                .setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
                .setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
                .setContentIntent(pendingIntent)//用户点击通知要执行的意图
                .setAutoCancel(true)//点击通知后,通知会自动消失
                .setDefaults(NotificationCompat.DEFAULT_ALL)//默认效果
                /*
                    通过 setStyle 方法,展示一张大图
                 */
                .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(
                        BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)))
                .build();
  • setPriority():设置通知的重要程度,该方法接收一个整型参数用于设置这条通知的重要程度,一共有5个常量值可选:
    1. PRIORITY_DEFAULT 表示默认的重要程度,和不设置效果一样;
    2. PRIORITY_MIN 表示最低的重要程度,系统只会在特定的场景才显示这条通知
    3. PRIORITY_LOW 表示较低的重要程度,系统可能会将这类通知缩小,或改变其显示顺序,将其排在更重要的通知之后
    4. PRIORITY_HIGH 表示较高的重要程度,系统可能会将这类通知放大,或改变其显示顺序,将其排在比较靠前的位置
    5. PRIORITY_MAX 表示最高的重要程度,这类通知消息必须要让用户立刻看到,甚至需要用户做出相应操作
        Notification notification = new NotificationCompat.Builder(SecondActivity.this)
                .setContentTitle("this is title")//设置通知的标题内容
                .setWhen(System.currentTimeMillis())//指定通知被创建的时间,毫秒为单位
                .setSmallIcon(R.mipmap.ic_launcher)//设置通知的小图标,注意只能使用纯 alpha 图层的图片进行设置
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//设置通知的大图标
                .setContentIntent(pendingIntent)//用户点击通知要执行的意图
                .setAutoCancel(true)//点击通知后,通知会自动消失
                .setDefaults(NotificationCompat.DEFAULT_ALL)//默认效果
                .setPriority(NotificationCompat.PRIORITY_MAX)//设置重要程度
                .build();

二、调用摄像头和拍照

调用摄像头

    public static final int TAKE_PHOTO = 1;
    private Uri imageUri;
                /*
                    创建 File 对象,用于存储拍照后的图片
                    getExternalCacheDir():SD 卡中专门用于存放当前应用缓存数据的位置
                        具体路径是:/sdcard/Android/data//cache
                    Android6.0开始,读写 sd 卡被列为了危险权限,需要动态申请,如果使用
                    应用关联的目录则可以不用动态申请。
                 */
                File fileImage = new File(getExternalCacheDir(), "output_image.jpg");
                try {
                    if (fileImage.exists()) {
                        fileImage.delete();
                    }
                    fileImage.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                /*
                    如果设备的系统版本 >= Android7.0
                    将 File 对象转换成封装过的 URI 对象
                    FileProvider.getUriForFile()方法接受三个参数:
                            参1:Context 对象
                            参2:任意字符串,(但是要和后面所说的 清单文件中的 Provider 中的 authorities 所匹配)
                            参3:File 对象
                    注意:因为从 Android7.0开始,直接使用本地真实路径的 URI 被认为是不安全的,会抛出一个
                    FileUriExposedException 异常,而 FileProvider 则是一种特殊的内容提供器,它使用了和
                    内容提供器类似的机制来对数据进行保护,可以选择性的将封装过的 URI 共享给外部,从而提高了应用安全性
                 */
                if (Build.VERSION.SDK_INT >= 24) {
                    imageUri = FileProvider.getUriForFile(SecondActivity.this,
                            "com.example.cameraalbum.fileprovider", fileImage);
                } else {
                    /*
                        将 File 转换成 URI 对象,这个 URI 对象包含着
                        图片本地真实路径
                     */
                    imageUri = Uri.fromFile(fileImage);
                }
                /*
                    启动相机
                 */
                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) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode){
            case TAKE_PHOTO:
                if(resultCode == RESULT_OK){
                    try {
                        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                        iv.setImageBitmap(bitmap);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
            default:
                break;
        }
    }
        
        
            
        
第一行代码(八)_第2张图片
image.png

注意:在 Android4.4系统之前,访问 SD 卡应用关联目录需要声明权限,从4.4系统开始就不再需要声明,为了兼容老版本,所以需要在清单文件中设置一下访问 SD 卡的权限。

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

从相册中选择照片

        /*
            因为相册中的照片是存储在 SD 卡上的,要从 SD 卡中读取照片,就需要申请权限
            该权限表示同时授予程序对 SD 卡读和写的能力
         */
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        } else {
            openAlbum();
        }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    openAlbum();
                } else {
                    Toast.makeText(this, "您决绝了请求", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }
    /**
     * 打开相册
     */
    private void openAlbum() {
        Intent intent = new Intent("android.intent.action.GET_CONTENT");
        intent.setType("image/*");
        startActivityForResult(intent, CHOOSE_PHOTO);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case CHOOSE_PHOTO:
                if (resultCode == RESULT_OK) {
                    /*
                        判断手机的版本
                        因为从 Android4.4版本开始,选取相册中的图片不在返回图片真实的 Uri 了,
                        而是一个封装过的 Uri,因此4.4版本以上的手机需要对这个 Uri 进行解析才行
                     */
                    if (Build.VERSION.SDK_INT >= 19) {
                        //4.4及以上的系统使用这个方法处理图片
                        handleImageOnKitKat(data);
                    } else {
                        //4.4以下的系统使用这个方法处理图片
                        handleImageBeforeKitKat(data);
                    }
                }
                break;
            default:
                break;
        }
    }
    /**
     * 4.4及以上的系统使用这个方法处理图片
     */
    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())) {
                /*
                    Uri 的 authority 是 media 格式的话,document id 还需要再次解析,
                    通过字符串分割的方式取出后半部分才能得到真正的数字 id
                 */
                String id = docId.split(":")[1];//解析出数字格式的 id
                //取出的 id 用于构建新的 Uri 和条件语句
                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);//根据图片路径显示图片
    }
    /**
     * 4.4一下的系统使用这个方法处理图片
     */
    private void handleImageBeforeKitKat(Intent data) {
        //这种 Uri 是没有进行封装过的,不需要任何解析
        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;
    }

    /**
     * 将图片显示到界面上
     * @param imagePath 图片的真实路径
     */
    private void displayImage(String imagePath) {
        if (!TextUtils.isEmpty(imagePath)) {
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            iv.setImageBitmap(bitmap);
        } else {
            Toast.makeText(this, "获取图片失败", Toast.LENGTH_SHORT).show();
        }
    }

三、播放多媒体文件

播放音频

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

  • setDataSource():设置要播放的音频文件的位置
  • prepare():开始播放之前调用这个方法完成准备工作
  • start():开始或继续播放音频
  • pause():暂停播放音频
  • reset():将 MediaPlayer 对象重置到刚刚创建的状态
  • seekTo():从指定的位置开始播放音频
  • stop():停止播放音频,调用后,MediaPlayer 对象无法再播放音频
  • release():释放掉与 MediaPlayer 对象相关的资源
  • isPlaying():判断当前 MediaPlayer 是否正在播放音频
  • getDuration():获取载入的音频文件的时长
    我们往手机里面导入一个音频文件
private MediaPlayer mediaPlayer = new MediaPlayer();
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
        }else{
            initMediaPlayer();
        }

    private void initMediaPlayer(){
        try {
            File file = new File(Environment.getExternalStorageDirectory(),"yzhaj.mp3");
            mediaPlayer.setDataSource(file.getPath());//指定音频文件的路径:/storage/emulated/0
            mediaPlayer.prepare();//让 MediaPlayer 进入准备状态
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            initMediaPlayer();
        }else{
            Toast.makeText(this, "您决绝了请求", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.tv_play:
                if(mediaPlayer.isPlaying()){
                    mediaPlayer.pause();
                }else{
                    mediaPlayer.start();
                }
                break;
            case R.id.tv_pause:
                if(mediaPlayer.isPlaying()){
                    mediaPlayer.pause();
                }
                break;
            case R.id.tv_stop:
                mediaPlayer.reset();
                initMediaPlayer();
                break;
            default:
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
//      千万不要忘了释放资源
        if(mediaPlayer != null){
            mediaPlayer.stop();
            mediaPlayer.release();
        }
    }
//在清单文件中声明权限

播放视频

  播放视频主要是使用 VideoView 来实现的

  • setVideoPath():设置播放的视频文件的位置
  • start():开始或继续播放视频
  • pause():暂停播放视频
  • resume():将视频重头开始播放
  • seekTo():从指定位置开始播放视频
  • isPlaying():判断当前是否正在播放视频
  • getDuration():获取视频文件的时长
  • suspend():释放所占用的资源

    
    private void videoView(){
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
        }else{
            initVideoView();
        }
    }

    private void initVideoView(){
        File file = new File(Environment.getExternalStorageDirectory(),"video.mp4");
        videoView.setVideoPath(file.getPath());//指定视频文件的路径
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            initVideoView();
        }else{
            Toast.makeText(this, "您决绝了权限", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.tv_play:
                if(videoView.isPlaying()){
                    videoView.pause();
                }else{
                    videoView.start();
                }
                break;
            case R.id.tv_pause:
                if(videoView.isPlaying()){
                    videoView.pause();
                }
                break;
            case R.id.tv_replay:
                videoView.resume();
                break;
            default:
                break;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(videoView != null){
            videoView.suspend();
        }
    }
//不要忘了声明权限

注意:VideoView 只是将 MediaPlayer 做了一层封装而已,而且 VideoView 并不是万能的视频播放器,它在视频格式的支持以及播放的效率方面都存在着较大的不足。

下一篇文章:https://www.jianshu.com/p/dc54268e0ef0

你可能感兴趣的:(第一行代码(八))