《第一行代码》读书笔记(七)

Author Avatar
wshunli 6月 04, 2018
  • 在其它设备中阅读本文章

《第一行代码》读书笔记 — 多媒体资源

《第一行代码》读书笔记(一)— 平台架构 (第1章)
《第一行代码》读书笔记(二)— 应用组件之 Activity (第2、4章)
《第一行代码》读书笔记(三)— 应用组件之 Service (第10章)
《第一行代码》读书笔记(四)— 应用组件之 BroadcastReceiver (第5章)
《第一行代码》读书笔记(五)— 应用组件之 ContentProvider (第7章)
《第一行代码》读书笔记(六)— 数据存储方案 (第6章)
《第一行代码》读书笔记(七)— 多媒体资源 (第8章)
《第一行代码》读书笔记(八)— 网络编程 (第9章)

第8章 丰富你的程序

使用通知

1、通知的基本用法

通知 Notification 是 Android 系统中比较特色的功能,使用方式也很灵活。

首先获取 NotificationManager 对象,然后构建 Notification 实例,最后使用 notify 方法显示通知。

NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new NotificationCompat.Builder(getApplicationContext(), "wshunli")
        .setContentTitle("Hello Notification !")
        .setContentText("This is content text")
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.mipmap.ic_launcher_round)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
        .setAutoCancel(true)
        .build();

notificationManager.notify(2333, notification);

以上是书籍代码思路,在 Android 8.0 上是不显示通知的,我们作相应的修改。

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

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    NotificationChannel channel = new NotificationChannel("2333", "wshunli",NotificationManager.IMPORTANCE_HIGH);
    notificationManager.createNotificationChannel(channel);
}
Notification notification = new NotificationCompat.Builder(getApplicationContext(), "wshunli")
        .setContentTitle("Hello Notification !")
        .setContentText("This is content text")
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.mipmap.ic_launcher_round)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
        .setAutoCancel(true)
        .setChannelId("2333")
        .build();

notificationManager.notify(2333, notification);

最终通知显示效果:

7.Notification.png

这时候点击通知是没有效果的,对通知 添加点击效果

使用 PendingIntent 来启动 Activity 、 Service 、发送广播等。

PendingIntent 会在何时和时机执行某个动作,而 Intent 会立即执行。

根据启动的 Android 组件不同可使用的方法也不同:

getActivity(Context, int, Intent, int),
getActivities(Context, int, Intent[], int), 
getBroadcast(Context, int, Intent, int), 
getService(Context, int, Intent, int);

其中最后一个参数取值也很丰富,FLAG_ONE_SHOT, FLAG_NO_CREATE, FLAG_CANCEL_CURRENT, FLAG_UPDATE_CURRENT 。

使用示例:

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

Notification notification = new NotificationCompat.Builder(getApplicationContext(), "wshunli")
        .setContentTitle("Hello Notification !")
        .setContentText("This is content text")
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.mipmap.ic_launcher_round)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
        .setAutoCancel(true)
        .setChannelId("2333")
        .setContentIntent(pendingIntent)
        .build();

使用 setContentIntent() 方法设置 PendingIntent 对象参数,点击即可跳转到 NotificationActivity 界面。

2、通知的进阶技巧

NotificationCompat.Builder 中有非常丰富的 API 来创建多样的通知效果。

2.1、设置声音

Notification.Builder setSound(Uri sound)

Notification notification = new NotificationCompat.Builder(getApplicationContext(), "wshunli")
        ···
        .setSound(Uri.parse("/system/media/audio/ringtones/Luna.ogg"))
        .build();

Android 8.0 要使用 NotificationChannel 设置:

void setSound (Uri sound, AudioAttributes audioAttributes)

NotificationChannel channel = new NotificationChannel("2333", "wshunli",NotificationManager.IMPORTANCE_HIGH);
channel.setSound(Uri.parse("/system/media/audio/ringtones/Luna.ogg"), null);
notificationManager.createNotificationChannel(channel);

2.2、设置震动

Notification.Builder setVibrate(long[] pattern)

Notification notification = new NotificationCompat.Builder(getApplicationContext(), "wshunli")
        ···
        .setVibrate(new long[]{0, 1000, 1000, 1000})
        .build();

其中数组表示:先停顿 0 ms ,震动 1000 ms ,再停顿 1000 ms , 最后震动 1000 ms ,后面以此类推。

需要申请权限:

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

Android 8.0 要使用 NotificationChannel 设置:

void setVibrationPattern (long[] vibrationPattern)

NotificationChannel channel = new NotificationChannel("2333", "wshunli",NotificationManager.IMPORTANCE_HIGH);
channel.setVibrationPattern(new long[]{0, 1000, 1000, 1000});
notificationManager.createNotificationChannel(channel);

3.3、设置 LED 灯

Notification.Builder setLights (int argb, int onMs, int offMs)

Notification notification = new NotificationCompat.Builder(getApplicationContext(), "wshunli")
        ···
        .setLights(Color.GREEN, 1000, 1000)
        .build();

Android 8.0 要使用 NotificationChannel 设置:

void enableLights (boolean lights)

channel.enableLights(true);

3.4、默认设置

前面那么多设置,嫌麻烦可以直接使用默认效果。

Notification notification = new NotificationCompat.Builder(getApplicationContext(), "wshunli")
        ···
        .setDefaults(NotificationCompat.DEFAULT_ALL)
        .build();

Android 8.0 此方法过时,还要用前面适用于 8.0 的方法。

3、通知的高级功能

3.1、首先看 setStyle() 方法

Notification.Builder setStyle (Notification.Style style)

首先来看显示一段长文字:

Notification notification = new NotificationCompat.Builder(getApplicationContext(), "wshunli")
        ···
        .setContentText("com.wshunli.notification.demo/com.wshunli.notification.demo.MainActivity")
        .build();

这样文字过长,会以省略号代替。

Notification notification = new NotificationCompat.Builder(getApplicationContext(), "wshunli")
        ···
        // 使用 setStyle() 方法
        .setStyle(new NotificationCompat.BigTextStyle().bigText("com.wshunli.notification.demo/com.wshunli.notification.demo.MainActivity"))
        .build();

这样文字会全部显示出来。

除了显示长文字外,通知还可以显示一张大图片:

Notification notification = new NotificationCompat.Builder(getApplicationContext(), "wshunli")
        ···
        .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.drawable.pano)))
        .build();

以上显示结果:

7.Notification.png7.Notification.png7.Notification.png
通知文字过长显示长文字效果显示大图片效果

3.2、通知的优先级

使用 setPriority 方法:

Notification.Builder setPriority (int pri)

Notification notification = new NotificationCompat.Builder(getApplicationContext(), "wshunli")
        ···
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        .build();

其中优先级可取值:PRIORITY_DEFAULT, PRIORITY_LOW, PRIORITY_MIN, PRIORITY_HIGH or PRIORITY_MAX.

Andorid 8.0 使用 NotificationManager 中 IMPORTANCE_DEFAULT, IMPORTANCE_HIGH, IMPORTANCE_LOW, IMPORTANCE_MAX, IMPORTANCE_MIN 代替。

方法也使用 NotificationChannel 中的 setImportance 方法:

void setImportance (int importance)

channel.setImportance(NotificationManager.IMPORTANCE_DEFAULT);

调用摄像头相册

1、调用摄像头拍照

首先应该申请相机权限:

<uses-permission android:name="android.permission.CAMERA" />
public static final int TAKE_PHOTO = 1;
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();
}
// 将 File 转化为 Uri 对象
if (Build.VERSION.SDK_INT < 24) {
    imageUri = Uri.fromFile(outputImage);
} else {
    imageUri = FileProvider.getUriForFile(MainActivity.this, "com.wshunli.camera.demo.fileprovider", outputImage);
}
// 启动相机程序
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO);

对于 Android 7.0 以前,File 转化为 Uri 比较简单,但是 7.0 以后认为直接使用真实路径的 Uri 是不安全的。

所以使用 FileProvider 内容提供器来对数据进行保护,可以选择性地将封装过的 Uri 共享给外部。

在 Manifest 中,添加 provider 内容提供器 :

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.wshunli.camera.demo.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

/app/src/main/res/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>

最后在 onActivityResult 中接收拍照完成后返回的数据:

@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));
                    Log.d(TAG, "onActivityResult: " + bitmap.getRowBytes());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            break;
        default:
            break;
    }
}

2、从相册中选择照片

同样应该声明权限,在系统版本 6.0 一样应该申请运行时权限。

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

打开相册还算比较简单:

public static final int CHOOSE_PHOTO = 2;

// 打开相册
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) {
    switch (requestCode) {
        ···
        case CHOOSE_PHOTO:
            if (resultCode == RESULT_OK) {
                // 判断手机系统版本号
                if (Build.VERSION.SDK_INT >= 19) {
                    // 4.4 及以上系统使用这个方法处理图片
                    handleImageOnKitKat(data);
                } else {
                    // 4.4 以下系统使用这个方法处理图片
                    handleImageBeforeKitKat(data);
                }
            }
            break;
        default:
            break;
    }
}

@TargetApi(19)
private void handleImageOnKitKat(Intent data) {
    String imagePath = null;
    Uri uri = data.getData();
    Log.d("TAG", "handleImageOnKitKat: uri is " + uri);
    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();
    }
    Log.d(TAG, "handleImageOnKitKat: " + imagePath);
}

private void handleImageBeforeKitKat(Intent data) {
    Uri uri = data.getData();
    String imagePath = getImagePath(uri, null);
    Log.d(TAG, "handleImageBeforeKitKat: " + 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;
}

播放多媒体文件

1、播放音频

播放音频主要使用 MediaPlayer 类。

MedaiaPlayer 设置资源有三种类型:

(1) 本地资源

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file);
mediaPlayer.start(); // no need to call prepare(); create() does that for you

(2) 通过 URI 共享的资源

Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();

(3) 通过网络 URL 资源

String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();

https://developer.android.com/guide/topics/media/mediaplayer

获取 MediaPlayer 实例就可以控制音频播放了。

mediaPlayer.prepare(); // 开始播放前准备工作
mediaPlayer.start(); // 开始播放
mediaPlayer.pause(); // 暂停播放
mediaPlayer.reset(); // 停止播放
mediaPlayer.seekTo(); // 指定位置播放
mediaPlayer.stop(); // 停止播放,后面不能再播放音频
mediaPlayer.release(); // 释放相关资源

2、播放视频

播放视频主要使用 VideoView 控件。

使用 setVideoPath (String path) 或者 setVideoURI (Uri uri) 设置视频资源。

然后就可以使用相应方法控制播放了.

videoView.start(); // 开始播放
videoView.pause(); // 暂停播放
videoView.resume(); // 重头开始播放
videoView.seekTo(); // 指定位置播放

对于音频视频播放还有官方框架 ExoPlayer :

https://github.com/google/ExoPlayer

参考资料
1、Android O: How to Use Notification Channels - Your Web App
http://forum.yourwebapp.mobi/android-o-how-to-use-notification-channels/
2、Android O 8.0系统下通知(Notification)、安装apk问题更新后的简单兼容写法 - CSDN博客
https://blog.csdn.net/weitao_666/article/details/79142592
3、Audio & Video | Android Developers
https://developer.android.com/guide/topics/media/

如果本文对您有所帮助,且您手头还很宽裕,欢迎打赏赞助我,以支付网站服务器和域名费用。 https://paypal.me/wshunli 您的鼓励与支持是我更新的最大动力,我会铭记于心,倾于博客。
本文链接:https://www.wshunli.com/posts/941f84ed.html