《第一行代码 第二版》第八章
《第一行代码 第二版》
本博客是对第一行代码的精简总结,仅供个人学习使用。如需系统学习请购买正版或者电子书籍。
链接附上 🔗图灵社区:https://www.ituring.com.cn/book/2744/
第 8 章 丰富你的程序——运用手机多媒体
8.1 使用通知
下拉列表可看到通知得信息
创建通知的详细步骤
首先需要一个NotificationManager来对通知进行管理,可以调用Context的getSystemService()
方法获取到。getSystemService()
方法接收一个字符串参数用于确定获取系统的哪个服务,这里我们传入Context.NOTIFICATION_SERVICE
即可。因此,获取NotificationManager的实例就可以写成:
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
support-v4库中提供了一个NotificationCompat
类,使用这个类的构造器来创建Notification
对象,就可以保证我们的程序在所有Android系统版本上都能正常工作了
Notification notification = new NotificationCompat.Builder(context)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.small_icon)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.large_icon))
.build();
5个设置方法,下面我们来一一解析一下。setContentTitle()
方法用于指定通知的标题内容,下拉系统状态栏就可以看到这部分内容。setContentText()
方法用于指定通知的正文内容,同样下拉系统状态栏就可以看到这部分内容。setWhen()
方法用于指定通知被创建的时间,以毫秒为单位,当下拉系统状态栏时,这里指定的时间会显示在相应的通知上。setSmallIcon()
方法用于设置通知的小图标,注意只能使用纯alpha图层的图片进行设置,小图标会显示在系统状态栏上。setLargeIcon()
方法用于设置通知的大图标,当下拉系统状态栏时,就可以看到设置的大图标了。
以上工作都完成之后,只需要调用NotificationManager的notify()
方法就可以让通知显示出来了。notify()
方法接收两个参数,第一个参数是id
,要保证为每个通知所指定的id
都是不同的。第二个参数则是Notification
对象,这里直接将我们刚刚创建好的Notification
对象传入即可。因此,显示一个通知就可以写成:
manager.notify(1, notification);
实现通知的点击效果,我们还需要在代码中进行相应的设置,这就涉及了一个新的概念:PendingIntent。
PendingIntent简单地理解为延迟执行的Intent。都可以用于启动活动、启动服务以及发送广播等。不同的是,Intent更加倾向于去立即执行某个动作,而PendingIntent更加倾向于在某个合适的时机去执行某个动作。
Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
NotificationManager manager = (NotificationManager) getSystemService
(NOTIFICATION_SERVICE);
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)
.build();
manager.notify(1, notification);
么系统状态上的通知图标还没有消失呢?是这样的,如果我们没有在代码中对该通知进行取消,它就会一直显示在系统的状态栏上。解决的方法有两种,一种是在NotificationCompat.Builder
中再连缀一个setAutoCancel()
方法,一种是显式地调用NotificationManager的cancel()
方法将它取消。
8.2.2 通知的进阶技巧
setSound()
方法吧,它可以在通知发出的时候播放一段音频,这样就能够更好地告知用户有通知到来。setSound()
方法接收一个Uri
参数,所以在指定音频文件的时候还需要先获取到音频文件对应的URI。
Notification notification = new NotificationCompat.Builder(this)
...
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg")))
.build();
我们还可以在通知到来的时候让手机进行振动,使用的是vibrate
这个属性。它是一个长整型的数组,用于设置手机静止和振动的时长,以毫秒为单位。下标为0的值表示手机静止的时长,下标为1的值表示手机振动的时长,下标为2的值又表示手机静止的时长,以此类推。所以,如果想要让手机在通知到来的时候立刻振动1秒,然后静止1秒,再振动1秒,代码就可以写成:
Notification notification = new NotificationCompat.Builder(this)
...
.setVibrate(new long[] {0, 1000, 1000, 1000 })
.build();
通知到来时,如果想要实现LED灯以绿色的灯光一闪一闪的效果,就可以写成:
Notification notification = new NotificationCompat.Builder(this)
...
.setLights(Color.GREEN, 1000, 1000)
.build();
你不想进行那么多繁杂的设置,也可以直接使用通知的默认效果,它会根据当前手机的环境来决定播放什么铃声,以及如何振动,写法如下:
Notification notification = new NotificationCompat.Builder(this)
...
.setDefaults(NotificationCompat.DEFAULT_ALL)
.build();
8.2.3 通知的高级功能
setStyle()
方法,这个方法允许我们构建出富文本的通知内容。也就是说通知中不光可以有文字和图标,还可以包含更多的东西。setStyle()
方法接收一个NotificationCompat.Style
参数,这个参数就是用来构建具体的富文本信息的,如长文字、图片等。
长文字:
setStyle()
方法中创建了一个NotificationCompat.BigTextStyle
对象,这个对象就是用于封装长文字信息的,我们调用它的bigText()
方法并将文字内容传入就可以了
大图片:
调用的setStyle()
方法,这次我们在参数中创建了一个NotificationCompat.BigPictureStyle
对象,这个对象就是用于设置大图片的,然后调用它的bigPicture()
方法并将图片传入。
优先级:
setPriority()
方法
PRIORITY_DEFAULT
表示默认的重要程度,和不设置效果是一样的;PRIORITY_MIN
表示最低的重要程度,系统可能只会在特定的场景才显示这条通知,比如用户下拉状态栏的时候;PRIORITY_LOW
表示较低的重要程度,系统可能会将这类通知缩小,或改变其显示的顺序,将其排在更重要的通知之后;PRIORITY_HIGH
表示较高的重要程度,系统可能会将这类通知放大,或改变其显示的顺序,将其排在比较靠前的位置;PRIORITY_MAX
表示最高的重要程度,这类通知消息必须要让用户立刻看到,甚至需要用户做出响应操作。
8.3 调用摄像头和相册
public class MainActivity extends AppCompatActivity {
public static final int TAKE_PHOTO = 1;
private ImageView picture;
private Uri imageUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button takePhoto = (Button) findViewById(R.id.take_photo);
picture = (ImageView) findViewById(R.id.picture);
takePhoto.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();
}
if (Build.VERSION.SDK_INT >= 24) {
imageUri = FileProvider.getUriForFile(MainActivity.this,
"com.example.cameraalbumtest.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);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case TAKE_PHOTO:
if (resultCode == RESULT_OK) {
try {
// 将拍摄的照片显示出来
Bitmap bitmap = BitmapFactory.decodeStream(getContent-
Resolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
}
8.3.1 调用摄像头拍照:
1.创建点击事件按钮和用于显示拍照后图片的Imageview及实例。
2.创建File对象,用于存储拍照后的照片,将其存放在当前引用的缓存数据中,图片名字为output_image.jpg
File outputImage = new File(getExternalCacheDir(),"output_image.jpg");
3.如果其存在,则删除,并createNewFile创建新文件实例。
4.1.如果系统低于7.0,在使用Uri.fromFile()方法将File对象转为Uri对象,Uri对象标识jpg文件真实路径;
4.2.如果大于等于7.0,则使用FileProvider.getUriForFile()方法,里面三个参数分别是Context对象,任意字符串以及刚刚创建的Image对象。
if (Build.VERSION.SDK_INT >=24){
imageuri = FileProvider.getUriForFile(MainActivity.this,"com.example.cameraalbumtest.fileprovider",outputImage);
}else{
imageuri = Uri.fromFile(outputImage);
}
5.构建Intent对象,将Action指定为android.media.action.IMAGE_CAPTURE,putExtra()填入刚刚得到的URI对象,最后调用startActivityForResult来启动活动。
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageuri);
startActivityForResult(intent,TAKE_PHOTO);
6.在AndroidManifest.xml中对内容提供器进行注册;(1)android:name固定(2)android:authorities与ileProvider.getUriForFile()第二个参数一致(3)在provider标签中指定meta-data来制定共享路径,并引用xml文件下的资源
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.cameraalbumtest.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
7.xml文件中external-path是指定Uri共享的,name属性可以随便填,path属性指的是共享的具体路径,设置空值就是将整个SD卡共享
<?xml version="1.0" encoding="utf-8" ?>
<paths xmlns:android = "http://schemas.android.com/apk/res/android">
<external-path name="my_images" path=""/>
</paths>
8.访问SD卡在4.4之前需要声明访问SD卡的权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
8.3.2从相册中选择照片
1.建立点击事件及布局,进行运行时权限处理,WRITE_EXTERNAL_STORAGE为程序对SD卡的读写能力。大于6.0动态申请权限后调用openAlbum()方法,小于6.0直接调用openAlbum()方法
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
openAlbum();
}
2.openAlbum中构建Intent对象,指定action为android.intent.action.GET_CONTENT,为Intent设定必要参数,调用startActivityForResult来打开相册程序选择照片,其中第二个参数为CHOOSE_PHOTO的2
private void openAlbum() {
Intent intent01 = new Intent("android.intent.action.GET_CONTENT");
intent01.setType("image/*");
startActivityForResult(intent01, CHOOSE_PHOTO);
}
3.这样在onActivityResult中进入CHOOSE_PHOTO进行处理,为了兼容新旧版本,4.4以上调handleImageOnKitKat方法; 4.4以下调handleImageBeforeKitKat方法。因为4.4以后选择相册中的照片不在返回真实的Uri,因此需要解析。
case CHOOSE_PHOTO:
if (resultCode == RESULT_OK) {
if (Build.VERSION.SDK_INT >= 19) {
handleImageOnKitKat(data);
} else {
handleImageBeforeKitKat(data);
}
}
4.1.handleImageOnKitKat解析了封装的Uri,(1)如果是document类型的Uri,则通过document id处理。若URi的authority是media格式,还需要进一步的解析,通过字符串分割获得真实ID,用ID构建新Uri和判断语句,将其传至getImagePath方法中。可以获取真实路径了(2)如果是content类型的uri,则使用普通方法去处理(3)如果是File类型的uri,直接获取图片路径即可,最后调用displayImage根据图片路径显示图片。
4.2.handleImageBeforeKitKat方法中Uri未经封装,无需解析,直接getIamgePath获取真实路径,再调用displayImage方法显示于界面。
private void displayImage(String imagePath) {
if(imagePath!=null){
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
iv_photos.setImageBitmap(bitmap);
}else {
Toast.makeText(this,"failed to get image",Toast.LENGTH_SHORT).show();
}
}
5.getIamgePath方法中通过Uri和selection来获取真实的路径,一列cursor对象的数据。
private String getIamgePath(Uri externalContentUri, String selection) {
String path = null;
//通过Uri和selection来获取真实的路径
Cursor cursor = getContentResolver().query(externalContentUri,null,selection,null,null);
if(cursor!=null){
if(cursor.moveToFirst()){
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
6.displayImage中显示图片至界面。
private void displayImage(String imagePath) {
if(imagePath!=null){
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
iv_photos.setImageBitmap(bitmap);
}else {
Toast.makeText(this,"failed to get image",Toast.LENGTH_SHORT).show();
}
}
8.4播放多媒体文件
(一)播放音频
1.类初始化时创建一个MediaPlayer的实例,然后在OnCreate方法是进行动态权限处理,动态申请WRITE_EXTERNAL_STORAGE权限。若用户拒绝,则Finish掉。若用户同意,调用initMediaPlayer方法
2.为MediaPlayer设置初始化操作,创建File指定文件路径,接着调用了setDataSource和prepare方法。注意:目前手机都有内置得超大内部存储,使用Environment.getExternalStorageDirectory()获取路径的时候就不能定位到SD卡了,而是默认检测成内部存储路径。而且不同厂商手机SD卡的挂载路径不一样,所以SD卡挂在目录也就不能统一。
private void initMediaPlayer() {
try {
//Environment.getExternalStorageDirectory()为内置内存的根目录
File file = new File(Environment.getExternalStorageDirectory(),"music.mp3");
mediaPlayer.setDataSource(file.getPath());//设置要播放视频文件的位置
mediaPlayer.prepare();//让MediaPlayer进入准备状态。
} catch (IOException e) {
e.printStackTrace();
}
}
3.点击播放按钮、暂停按钮、停止按钮的响应事件,判断MediaPlayer当前有没有播放视频,若没有,则开始播放。
switch (v.getId()){
case R.id.btn_play:
if(!mediaPlayer.isPlaying()){
mediaPlayer.start();//开始播放
}
break;
4.最后在OnDestory方法中,分别调用stop和release方法以释放资源。
@Override
protected void onDestroy() {
super.onDestroy();
if(mediaPlayer!=null){
mediaPlayer.stop();
mediaPlayer.release();
}
}
这样就可以播放了。
(二)播放视频
几乎一模一样,只是把MediaPlayer改为VideoView来实现。
区别地方在于:
(1)无需再初始化Prepare,直接setVideoPath之后就可以start了;
(2)重头播放用Resume,
case R.id.btn_stop:
if(videoView.isPlaying()){
videoView.resume();
}
break;
(3)释放资源用suspend方法。
@Override
protected void onDestroy() {
super.onDestroy();
if(videoView!=null){
videoView.suspend();
}
}