《第一行代码》读书笔记 – 多媒体资源
《第一行代码》读书笔记(一)– 平台架构 (第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);
|
最终通知显示效果:

这时候点击通知是没有效果的,对通知 添加点击效果 。
使用 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(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();
|
以上显示结果:
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 outputImage = new File(getExternalCacheDir(), "output_image.jpg"); try { if (outputImage.exists()) { outputImage.delete(); } outputImage.createNewFile(); } catch (IOException e) { e.printStackTrace(); }
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) { handleImageOnKitKat(data); } else { 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)) { String docId = DocumentsContract.getDocumentId(uri); if("com.android.providers.media.documents".equals(uri.getAuthority())) { String id = docId.split(":")[1]; 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())) { imagePath = getImagePath(uri, null); } else if ("file".equalsIgnoreCase(uri.getScheme())) { 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; 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();
|
(2) 通过 URI 共享的资源
Uri myUri = ....; MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setDataSource(getApplicationContext(), myUri); mediaPlayer.prepare(); mediaPlayer.start();
|
(3) 通过网络 URL 资源
String url = "http://........"; MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setDataSource(url); mediaPlayer.prepare(); 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/