第一行代码读书笔记 8 -- 手机多媒体

本篇文章主要介绍以下几个知识点:

  • 通知的使用;
  • 调用系统相机拍照或调用相册选取照片;
  • 播放多媒体文件。
图片来源于网络

8.1 使用通知

当某个应用程序希望向用户发送一些提示消息,而该应用程序又不在前台运行时,就可借助通知(Notification)来实现。

8.1.1 通知的基本用法

通知的用法灵活,既可以在活动里创建,也可在广播接收器里创建,还可在服务里创建。

无论在哪里创建通知,整体的步骤都是相同的。接下来学习下创建通知的步骤,代码如下:

public class NotificationActivity extends AppCompatActivity implements View.OnClickListener {

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

        Button btn_send_notice = (Button) findViewById(R.id.btn_send_notice);
        btn_send_notice.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_send_notice:
                // 1. 获取 NotificationManager 实例来对通知进行管理
                NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                // 2. 使用 Builder 构造器来创建 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();
                // 3. 显示通知. 其中notify()的两个参数:第一个是id,要保证为每个通知所指定的id都是不同的;
                // 第二个参数是 Notification 对象
                manager.notify(1,notification);
                break;

            default:
                break;
        }
    }
}

上述代码,界面上就一个 发送通知 按钮,在按钮的点击事件里面完成了通知的创建工作。运行程序,点击按钮效果如下:

通知的基本用法

接下来实现通知的点击效果,这里要用到 PendingIntent ,即在某个合适的时机去执行某个动作。PendingIntent 实例可由 getActivity()、getBroadcast()、getServices() 等方法获取,其接收的参数:第一个是 Context;第二个一般用不到传0即可;第三个是 Intent 对象;第四个用于确定 PendingIntent 的行为,一般传0即可。

首先,准备好一个点击通知跳转的活动 NotificationTestActivity ,然后,修改 NotificationActivity 的代码如下:

public class NotificationActivity extends AppCompatActivity implements View.OnClickListener {

    . . . 

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_send_notice:
                Intent intent = new Intent(this,NotificationTestActivity.class);
                PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);

                // 1. 获取 NotificationManager 实例来对通知进行管理
                NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                // 2. 使用 Builder 构造器来创建 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))
                        .setContentIntent(pi) // 传入pi
                        .build();
                // 3. 显示通知
                manager.notify(1,notification);
                break;

            default:
                break;
        }
    }
}

现在重新运行程序并点击按钮,效果如下:

点击通知后的效果

细心的你会发现系统状态栏上的通知图标还没消失,解决方法有两种,一种是在 NotificationCompat.Builder 中再连缀一个 setAutoCancel() 方法,如下:

Notification notification = new NotificationCompat.Builder(this)
        . . .
        .setAutoCancel(true)
        .build();

另外一种是显式调用 NotificationManager 的 cancel()方法,在点击通知跳转的活动 NotificationTestActivity 中添加如下代码:

public class NotificationTestActivity extends AppCompatActivity {

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

        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        manager.cancel(1); // 传入创建通知时指定的id
    }
}

8.1.2 通知的进阶用法

上面提到了通知的基本用法,接下来介绍一些通知的其他技巧,比如:

  • 在通知发出时播放一段音频,调用 setSound() 方法:
Notification notification = new NotificationCompat.Builder(this)
        . . .
        .setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg"))) // 在音频目录下选个音频文件
        .build();
  • 在通知到来时让手机振动,设置 vibrate 属性:
Notification notification = new NotificationCompat.Builder(this)
        . . .
        .setVibrate(new long[]{0,1000,1000,1000}) // 数组下标0表静止的时长,下标1表振动的时长,下标2表静止的时长,以此类推
        .build();

当然别忘了声明振动权限:

<uses-permission android:name="android.permission.VIBRATE" />
  • 在通知到来时显式手机 LED 灯,调用 setLights() 方法:
Notification notification = new NotificationCompat.Builder(this)
        . . .
        .setLights(Color.GREEN,1000,1000) // 三个参数:LED 灯的颜色、灯亮时长、灯暗时长
        .build();
  • 当然也可直接使用默认效果,如下:
Notification notification = new NotificationCompat.Builder(this)
        . . .
        .setDefaults(NotificationCompat.DEFAULT_ALL)
        .build();

8.1.3 通知的高级功能

上面提到了通知的进阶用法,接下来介绍一些通知的高级功能,比如:

  • NotificationCompat.Builder 类中的 setStyle() 方法
      这个方法允许我们构建出富文本的通知内容,setStyle() 方法接收一个 NotificationCompat.style 参数,这个参数用来构造具体的富文本信息,如长文字、图片等。

在通知当中显示一段长文字,代码如下:

Notification notification = new NotificationCompat.Builder(this)
        . . .
        // 在setStyle方法中创建NotificationCompat.BigTextStyle对象,调用其bigText()方法传入文字即可
        .setStyle(new NotificationCompat.BigTextStyle().bigText("红红火火恍恍惚惚," +
                 "哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈呵呵,红红火火恍恍惚惚" +
                 "哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈"))   
        .build();

效果如图所示:


通知中显示长文字的效果

在通知当中显示一张图片,代码如下:

Notification notification = new NotificationCompat.Builder(this)
        . . .
        .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.
                    decodeResource(getResources(),R.drawable.big_image)))))   
        .build();

效果如图所示:

通知中显示图片的效果
  • NotificationCompat.Builder 类中的 setPriority() 方法
      这个方法用于设置通知的重要程度,setPriority() 方法接收一个整型参数。

将通知的重要程度设置最高,代码如下:

Notification notification = new NotificationCompat.Builder(this)
        . . 
        // 一共有5个常量可选:PRIORITY_DEFAULT默认、PRIORITY_MIN最低、PRIORITY_LOW较低、
        // PRIORITY_HIGH较高、PRIORITY_MAX最高
        .setPriority(NotificationCompat.PRIORITY_MAX)   
        .build();

效果如图所示:

发送一条重要通知的效果

8.2 调用摄像头和相册

调用摄像头拍照以及从相册中选择照片的相关代码如下:

public class TakePhotoActivity extends AppCompatActivity {

    private Button btn_take_photo;   // 调用摄像头拍照
    private Button btn_select_photo; // 从相册中选照片
    private ImageView iv_show_photo; // 展示照片

    public static final int TAKE_PHOTO = 1;
    public static final int CHOOSE_PHOTO = 2;
    private Uri imageUri;

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

        btn_take_photo = (Button) findViewById(R.id.btn_take_photo);
        btn_select_photo = (Button) findViewById(R.id.btn_select_photo);
        iv_show_photo = (ImageView) findViewById(R.id.iv_show_photo);

        // 调用相机拍照
        btn_take_photo.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){
                    // getUriForFile() 方法接收三个参数:Context对象、任意唯一的字符串、File对象
                    imageUri = FileProvider.getUriForFile(TakePhotoActivity.this, "com.wonderful." +
                            "myfirstcode.chapter8.TakePhotoActivity.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);
            }
        });

        // 打开相册选照片
        btn_select_photo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ActivityCompat.checkSelfPermission(TakePhotoActivity.this,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    ActivityCompat.requestPermissions(TakePhotoActivity.this,
                            new String[]{android.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 {
                    ToastUtils.showShort("你拒绝了权限请求");
                }
                break;

            default:
                break;
        }
    }

    @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));
                        iv_show_photo.setImageBitmap(bitmap);
                    }catch (FileNotFoundException e){
                        e.printStackTrace();
                    }
                }

            case CHOOSE_PHOTO:
                if (resultCode == RESULT_OK){
                    // 判断手机系统版本号
                    if (Build.VERSION.SDK_INT >= 19){
                        // 4.4及以上系统使用这个方法处理图片
                        handleImageOnKitKat(data);
                    }else {
                        // 4.4以下系统使用这个方法处理图片
                        handleImageBeforeKitKat(data);
                    }
                }

            default:
                break;
        }
    }

    /**
     *  4.4及以上系统处理图片方法
     * @param data
     */
    @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类型到1Uri,则直接获取图片路径即可
            imagePath = uri.getPath();
        }
        // 根据图片路径显示图片
        displayImage(imagePath);
    }

    /**
     *  4.4以下系统处理图片方法
     * @param data
     */
    private void handleImageBeforeKitKat(Intent data) {
        Uri uri = data.getData();
        String imagePath = getImagePath(uri,null);
        displayImage(imagePath);
    }

    /**
     * 通过Uri和selection来获取真实的图片路径
     * @param uri
     * @param selection
     * @return
     */
    private String getImagePath(Uri uri,String selection){
        String path = null;
        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 (imagePath != null){
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            iv_show_photo.setImageBitmap(bitmap);
        }else {
            ToastUtils.showShort("选取图片失败");
        }
    }
}

上述代码中,调用摄像头拍照时用到了内容提供器,所以别忘了在 AndroidManifest.xml 中对内容提供器进行注册,如下:

<!-- android:name 属性的值是固定的
     android:authorities 属性的值要和FileProvider.getUriForFile()方法中的第二个参数一致
     <meta-data>来指定Uri的共享路径 -->
<provider
    android:authorities="com.wonderful.myfirstcode.chapter8.TakePhotoActivity.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>

上面引用了一个 @xml/file_paths 资源是不存在的,需要另外创建它。新建 file_paths.xml 文件,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- external_path指定Uri共享,其name的值可随便填,path是指共享的具体路径 -->
    <external_path name = "my_images" path = ""/>
</paths>

当然也别忘了声明访问SD卡的权限:

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

注意事项:在实际开发中最好根据项目的需求先对照片进行适当的压缩,然后再加载到内存中。

8.3 播放多媒体文件

  在安卓中播放音频文件一般用 MediaPlayer 类来实现,播放视频文件主要用 VideoView 类来实现。以下是两个类的常用方法:

播放多媒体文件方法

  实现播放多媒体代码相对简单,这里播放音频和视频写在一块了,代码如下:

public class MediaPlayActivity extends AppCompatActivity implements View.OnClickListener {

    private Button music_play,music_pause,music_stop;// 音频播放、暂停、停止
    private Button video_play,video_pause,video_replay;// 视频播放、暂停、重新播放

    // 音频播放 创建MediaPlayer实例
    private MediaPlayer mediaPlayer = new MediaPlayer();

    // 视频播放显示
    private VideoView videoView;

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

        initMediaPlayerView();

        if (ActivityCompat.checkSelfPermission(MediaPlayActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MediaPlayActivity.this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }else {
            initMediaPlayer(); // 初始化MediaPlayer
            initVideoPath(); // 初始化MediaPlayer
        }
    }



    @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();
                    initVideoPath();
                }else {
                    ToastUtils.showShort("你拒绝了权限请求");
                }
                break;

            default:
                break;
        }
    }

    /**
     * 初始化音频播放器
     */
    private void initMediaPlayer() {
        File file = new File(Environment.getExternalStorageDirectory(),"music.mp3");//事先放好的音频文件
        try {
            mediaPlayer.setDataSource(file.getPath()); // 指定音频文件的路径
            mediaPlayer.prepare();// 让 MediaPlayer 进入到准备状态
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化视频播放器
     */
    private void initVideoPath() {
        File file = new File(Environment.getExternalStorageDirectory(),"movie.mp4");//事先放好的视频文件
        videoView.setVideoPath(file.getPath());   // 指定视频文件的路径
    }


    private void initMediaPlayerView() {
        music_play = (Button) findViewById(R.id.music_play);
        music_pause = (Button) findViewById(R.id.music_pause);
        music_stop = (Button) findViewById(R.id.music_stop);

        music_play.setOnClickListener(this);
        music_pause.setOnClickListener(this);
        music_stop.setOnClickListener(this);

        videoView = (VideoView) findViewById(R.id.video_view);
        video_play = (Button) findViewById(R.id.video_play);
        video_pause = (Button) findViewById(R.id.video_pause);
        video_replay = (Button) findViewById(R.id.video_replay);

        video_play.setOnClickListener(this);
        video_pause.setOnClickListener(this);
        video_replay.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            // 音频播放
            case R.id.music_play:
                if (!mediaPlayer.isPlaying()){
                    mediaPlayer.start(); // 开始播放
                }
                break;
            case R.id.music_pause:
                if (!mediaPlayer.isPlaying()){
                    mediaPlayer.pause(); // 暂停播放
                }
                break;
            case R.id.music_stop:
                if (!mediaPlayer.isPlaying()){
                    mediaPlayer.stop(); // 停止播放
                }
                break;

            // 视频播放
            case R.id.video_play:
                if (!videoView.isPlaying()){
                    videoView.start(); // 开始播放
                }
                break;
            case R.id.video_pause:
                if (!videoView.isPlaying()){
                    videoView.pause(); // 暂停播放
                }
                break;
            case R.id.video_replay:
                if (!videoView.isPlaying()){
                    videoView.resume(); // 重新播放
                }
                break;
        }

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null){
            mediaPlayer.stop();
            mediaPlayer.release();// 释放资源
        }
        if (videoView != null){
            videoView.suspend();  // 释放资源
        }
    }
}

  当然别忘了声明权限:

<!-- 访问SD卡权限 -->
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

  本章内容相对简单,下一章将学习网络编程的知识。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容