文章目录
Ch.VIII:
8.2 使用通知:
由于Android 8.0使用了渠道机制, 导致没有渠道的通知无法显示, 所以, 直接看作者的新文章吧:
关于Android 8.0新增的通知栏渠道NotificationChannle の介绍与使用
使用通知notification的标准操作(适配Android 8.0+):
-
创建NotificationManager类对象, 用于获取系统的通知服务:
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
-
创建频道NotificationChannel, 并在NotificationManager中注册:
NotificationChannel channel = new NotificationChannel(id, name, importance); notificationManager.createNotificationChannel(channel);
渠道Channle一旦使用NotificationManager 的 createNotificationChannel()方法注册后, 就会一直在系统中有记录, 除非卸载应用重装
所以, 后头使用NotificationCompat.Builder创建notification时, 可以直接使用上头注册好的Channel
-
使用建筑者模式构筑Notification类实例:
由于通知Notification在每个API版本中更新很频繁, 所以这里使用 NotificationCompat.Builder 构筑兼容性的builder, 并在builder中设置生成的Notification的各个属性
new NotificationCompat.Builder(MainActivity.this, notificationIDList.get(0)) .setContentTitle("这是一条礼貌问候") .setContentText("NM$L") .setWhen(System.currentTimeMillis()) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
注意Android 8.0 之后, 有些设置不再有用, 需要到NotificationChannel中设置
-
使用NotificationCompat.Builder类的build()方法创建指定的Notification对象:
Notification notification = builder.build();
-
使用NotificationManager的notify() 方法发出通知:
notificationManager.notify(0, notification);
- 第一个参数为ID, 只需要确保全局唯一性即可
- 第二个参数为需要发出的Notification
注意点:
-
如果需要设置其他与Channel相关的属性, 如震动, 就需要到Channel中设置, 并且, 相关设置还需要足够的Channel优先级才有效(如震动&弹窗)
channel.setVibrationPattern(new long[]{0,200,1000,400,500,100,600});
其他还有很多属性, 基本上API中介绍的都需要转到Channel中进行设置, 单独对Notification进行设置不再有用
-
Channel一旦注册后, 基本上是无法delete的, 即时使用notificationManager.deleteNotificationChannel(id), 也无法删除并创建新的
删除的方法只能是通过卸载APP再重装, 亦或者不断的增大ID
-
所有使用相同Channel的Notification都会被系统折叠, 以减少占用的空间:
-
用户可以到设备的权限管理中心对APP的通知权限进行调整, 主要是调整各个通道的属性
-
由于用户对Channel有绝对的控制权, 并且Channel创建后, 程序无法修改, 所以当程序必须需要某些通知权限时, 可以通过检测的方法, 提醒用户开始权限:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = manager.getNotificationChannel("chat"); if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) { Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel.getId()); startActivity(intent); Toast.makeText(this, "请手动将通知打开", Toast.LENGTH_SHORT).show(); } }
另外还有很多的设置点, 可以看作者大大的新博客, 下头对此有较多的例举
为Notification加上PendingIntent:
PendingIntent可以理解为延时执行的Intent, 具有与Intent类似的功能
使用PendingIntent类的静态方法获取PendingIntent对象:
- getActivity(Context, int, Intent, int)------->跳转到一个activity组件、
- getBroadcast(Context, int, Intent, int)------>打开一个广播组件
- getService(Context, int, Intent, int)-------->打开一个服务组件。
其中:
-
第一个参数为Context对象, 没啥好说的
-
第二个参数通常传入0
-
第三个参数为Intent对象, 用来构建将要执行的意图
-
第四个参数有这4个static量可选:
通常传入0即可
创建了PendingIntent对象后, 使用NotificationCompat.Builder的setContentIntent()方法将PendingIntent对象加入builder中
public Builder setContentIntent(PendingIntent intent) {
mContentIntent = intent;
return this;
}
而后, 可以考虑设置点击后自动删除通知:
-
new NotificationCompat.Builder(MainActivity.this, notificationIDList.get(1)) .setContentTitle("这是一条推荐信息") .setContentText("WDNMD真就白给啊") .setWhen(System.currentTimeMillis()) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) .setContentIntent(pendingIntent) .setAutoCancel(true); //使用这个设置自动关闭
-
或者是在程序中显示的进行关闭:
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.cancel(1);
cancel()方法接受一个Channel的全局唯一ID, 指定关闭哪个Channel
通知的进阶技巧&高级功能:
这里就是介绍NotificationCompatible.Builder中的各个设置Notification属性的方法:
这个到时候需要在查看吧
8.3调用摄像头和相册:
然后下头的就都是狗屁!!!
-
首先是动态申请权限, 如果Android 版本>=6.0 就需要动态权限申请:
和之前介绍的一样, 这里就不重复了
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, 1); Toast.makeText(MainActivity.this, "请打开相机权限", Toast.LENGTH_SHORT).show(); } else { openCamera(); Toast.makeText(MainActivity.this, "已获取相机权限", Toast.LENGTH_SHORT).show(); } }
@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) { Toast.makeText(this, "允许使用相机", Toast.LENGTH_SHORT).show(); openCamera(); } else { Toast.makeText(this, "拒绝使用相机", Toast.LENGTH_SHORT).show(); } break; } case 2: { break; } default: } }
-
而后是为相机拍摄的照片创建一个储存文件:
首先需要了解一下File类, 这玩意在之前的Java学习中没看到…
这里使用File类的父路径&子路径的构造函数:
File outputImage = new File(getExternalCacheDir(), "outputImage.jpg");
其中getExternalCacheDir()方法获取当前APP在SD卡中的数据缓存路径, 具体的路径是
/sdcard/Android/data/<package name>/cache
此路径不需要申请SD卡读写权限
如果选择其他目录, 需要申请动态权限:
-
而后, 获取储存文件的Uri:
而后, 由于Android 7.0 又对权限进行了限制, 所以需要一定的额外操作:
主要是7.0之后, 认为提供真实路径的Uri是不安全的, 需要使用特殊的内容提供器FileProvider对Uri进行一定的封装, 并选择性的提供给外部, 提高应用的安全性:
if (Build.VERSION.SDK_INT >= 24) { // imageUri = Uri.fromFile(outputImage); imageUri = FileProvider.getUriForFile(this, "com.example.cameraalbumdemo.fileProvider", outputImage); } else { imageUri = Uri.fromFile(outputImage); }
注意这里使用的不是
android.support.v4.content.FileProvider
, 转而使用androidx.core.content.FileProvider
, 由于前者不知道啥原因找不到…, 所以改用后者, 效果相同<provider android:name="androidx.core.content.FileProvider" android:authorities="com.example.cameraalbumdemo.fileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
- android:name属性的值是固定的
- android:authorities属性的值必须要和刚才FileProvider.getUriForFile()方法中的第二个参数一致
- 另外,这里还在
<provider>
标签的内部使用<meta-data>
来指定Uri的共享路径,并引用了一个@xml/file_paths
资源。当然,这个资源现在还是不存在的,下面我们就来创建它。
添加一个xml文件, 注意这个文件的文件名需要与提供的路径资源相同
首先要在res的文件夹下建立一个xml的文件夹,再建立一个file_paths.xml,如图所示:
file_path.xm的内容如下:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="external_files" path=""/> </paths>
其中,external-path 就是用来指定Uri共享的,name属性的值可以随便填,path属性的值表示共享的具体路径。这里设置空值就表示将整个SD卡进行共享,当然你也可以仅共享我们存放output image.jpg这张图片的路径。
-
使用Intent打开相机:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); if (intent.resolveActivity(getPackageManager()) != null) { startActivityForResult(intent, TAKE_PHOTO); }
这里与文中不同, 文中使用的是
Intent intent=new Intent("android.media.action.IMAGE_CAPTURE"); intent. putExtra(MediaStore. EXTRA OUTPUT, imageUri); startActivityForResult(intent, TAKE_PHOTO);
主要区别为使用MediaStore.ACTION_IMAGE_CAPTURE, 基本上都是使用这个了, 直接使用String的
android.media.action.IMAGE_CAPTURE
很少而后是后头启用相机的方式, 实际上就是多个判定, 保证获取到了相机…
-
最后是处理回调
主要做的是将储存的图片重新读取, 并显示到ImageView上:
@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)); imageView.setImageBitmap(bitmap); } catch (FileNotFoundException e) { e.printStackTrace(); } }else{ Toast.makeText(this,"已取消拍摄",Toast.LENGTH_SHORT).show(); } break; } default: } }
其中使用的方法在Ch.7访问其他程序中的数据中有学到:
-
首先是判断是否完成了拍摄, 如果在使用相机时back取消拍摄, resultCode就是RESULT_CANCELD了
-
而后是开一个输入流, 指向储存的照片文件, 并解析为bitmap显示在ImageView上
-
8.4 播放多媒体文件:
播放音频文件:
这里就是使用Android MediaPlayer进行多媒体的播放
这里对MediaPlayer进行了非常详细的解释, 基本上不需要看书了
常用的MediaPlayer方法:
而后, 对于Path路径的获取, 这里使用一个轻量化的FilePicker框架, 上手非常简单, 并且其中也有较多的说明与DEMO供高级功能, 基本上没啥特殊需求的话, 完全够用
播放视频文件:
这里使用VideoView进行视频播放, 使用方法与MediaPlayer有点类似:
常用方法:
上手非常简单, 并且其中也有较多的说明与DEMO供高级功能, 基本上没啥特殊需求的话, 完全够用](https://github.com/rosuH/AndroidFilePicker)
播放视频文件:
这里使用VideoView进行视频播放, 使用方法与MediaPlayer有点类似:
常用方法:
常用方法也是很简单, 初始化后, 用setVideoPath() 方法设置个路径就可以和MediaPlayer一样用start()播放了, 更多高级用法看这里, 现在暂时用不到, 就放过吧