一、使用通知(Notification)
通知(Notification)是Android系统中比较有特色的一个功能,当某个应用程序希望向用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知来实现。发出一条通知后,手机最上方的状态栏中会显示一个通知的图标,下拉状态栏后可以看到通知的详细内容。Android的通知功能获得了大量用户的认可和喜爱,就连iOS系统也在5.0版本之后也加入了类似的功能。
1.1、通知的基本用法
通知的用法还是比较灵活的,既可以在活动里创建,也可以在广播接收器里创建,当然还可以在服务里创建。相比于广播接收器和服务,在活动里创建通知的场景还是比较少的,因为一般只有当程序进入到后台的时候我们才需要使用通知。但是不管在哪儿创建,整体的步骤都是相同的:
1、首先需要一个NotificationManager来对通知进行管理,可以调用Context的:getSystemService()方法获取到。getSystemService()方法接收一个字符串参数用于确定获取系统的哪个服务,这里我们传入:Context.NOTIFICATION_SERVICE 即可,因此,获取:NotificationManager的实例就可以写成:
2、接下来需要使用一个Builder构造器来创建Notification对象,但是问题在于,几乎Android系统的每一个版本都会对通知这部分功能进行或多或少的修改,API不稳定性问题在通知上面突显得尤其严重,为了解决这个问题,我们使用support库中提供的兼容API。support-v4库中提供了一个NotificationCompat类,使用这个类的构造器来创建Notification对象,就可以保证我们的程序在所有Android系统版本上都能正常工作了。代码如下:
上面的代码只是创建了一个空的Notification对象,并没有什么实际的作用,我们可以在最终的build()方法之前连缀任意多的设置方法来创建一个丰富的Notification对象,先来看一些最基本的设置:
上述代码中一共调用了5个设置方法:
setContentTitle()方法用于指定通知的标题内容,下拉系统状态栏就可以看到这部分内容。
setContentText()方法用于指定通知的正文内容,同样下拉系统状态栏就可以看到这部分内容。
setWhen()方法用于指定通知被创建的时间,以毫秒为单位,当下拉系统状态栏时,这里指定的时间会显示在相应的通知上。
setSmallIcon()方法用于设置通知的小图标,注意只有使用纯alpha图层的图片进行设置,小图标会显示在系统状态栏上。
setLargeIcon()方法用于设置通知的大图标,当下拉系统状态栏时,就可以看到设置的大图标了。
3、以上工作完成后,只需要调用NotificationManager的notify()方法就可以让通知显示出来了。notify()方法接收两个参数,第一个是id,要保证为每个通知所指定的id都是不同的,第二个参数则是Notification对象,这里直接将我们刚刚创建好的Notification对象传入即可。因此显示一个通知就可以写成:
到这里就已经把创建通知的每一个步骤都分析完了,接下来就通过实际的例子来体验一下
第一步:新建一个NotificationTest项目,设置一个用于发送通知的按钮:
第二步:在MainActivity中实现功能逻辑
第三步:运行程序,点击按钮,我们看到在系统状态栏的最左边看到一个小图标,下拉系统状态栏可以看到该通知的详细信息。
这里我们发现,如果点击通知信息,没有任何效果,平时我们使用手机的时候可以点击通知查看详情,要实现通知的点击的效果,我们还需要在代码中设置,这里涉及到一个新的概念:PendingIntent.
PendingIntent从名字上看和Intent有些相似,它们之间也确实存在这不少共同点,比如,它们都可以去指明某个“意图”,都可以启动活动、启动服务以及发送广播等。不同的是,Intent更倾向于去立即执行某个动作,而PendingIntent更加倾向于在某个合适的时机去执行某个动作。所以,也可以把PendingIntent简单的理解为延迟执行的Intent。
PendingIntent的用法也很简单,它只要提供了几个静态方法用于获取PendingIntent的实例,可以根据需求来选择是使用getActivity()方法、getBroadcast()方法、getService()方法。这几个方法所接收的参数都是相同的,第一个参数依旧是Context;第二个参数一般用不到,通常都是传入0即可;第三个参数是一个Intent对象,我们可以通过这个对象构建出PendingIntent的“意图”,第四个参数用于确定PendingIntent的行为,有:FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCLE_CURRENT、FLAG_UPDATE_CURRENT这四种可选,每种值的具体含义可以查看文档,通常情况下这个参数传入0即可。
对PendingIntent有了一定的了解后,再看一下前面的NotificationCompat.Builder,这个构造器还可以再连缀一个:setContentIntent()方法,接收的参数正是一个PendingIntent对象,因此这里就可以通过PendingIntent构建出一个延时执行的“意图”,当用户点击这条通知时就会执行相应的逻辑。
现在来优化Notification项目,给刚才的通知功能加上点击功能,让用户点击它的时候可以启动另外一个活动。
第四步:创建另一个活动:NotificationActivity,布局名为:notification_layout,在布局文件中设置一个TextView
第五步:在MainActivity中添加通知的点击功能:
可以看到,这里先使用Intent表达出我们想要启动NotificationActivity的“意图”,然后将构建好的Intent对象传入到PendingIntent的:getActivity()方法里,以得到PendingIntent的实例,接着在NotificationCompat.Builder中调用:setContentIntent()方法,把它作为参数传入即可。
第六步:重新运行程序,点击按钮,发出通知后,下拉系统状态栏,点击通知,就会看到NotificationActivity活动的界面了
但是我们看到,进入到NotificationActivity活动界面后,通知图标并没有消失,这是因为我们没有在代码中对该通知进行取消,它就会一直显示在系统的状态栏上,解决方法有两种:一种是在NotificationCompat.Builder中再连缀一个:setAutoCancel()方法;一种是显式地调用NotificationManager的:cancel()方法。两种方法使用如下:
这里在cancel()方法中传入了1,这个1是通知的id,我们在创建通知的时候给这条通知设置的id就是1(如下所示),因此,如果我们想要取消哪条通知,在cancel()方法就传入该通知的id就行了。
1.2、通知的进阶技巧
上一小节我们创建的通知属于最基本的通知,实际上,NotificationCompat.Builder中提供了非常方法的API来让我们创建出更加多样的通知效果,这里我们就来看看一些比较常用的API:
1、setSound()方法:它可以咋通知发出的时候播放一段音频,这样就能够更好的告知用户有通知到来。setSound()方法接收一个Uri参数,所以在指定音频文件的时候,还需要先获取到音频文件对应的URI,比如说,每个手机的:/system/media/audio/ringtones 目录下都有很多的音频文件,我们可以从中随便选一个音频文件,那么在代码中就可以这样指定:
2、除了允许播放音频外,我们还可以在通知到来的时候让手机进行震动,使用的是:vibrate这个属性,它是一个长整型的数组,用于设置手机静止和震动的时长,以毫秒为单位,下标为0的值表示手机静止的时长,下标为1的值表示手机震动的时长,下标为2的值又表示手机静止的时长,以此类推,所以,如果想要手机在通知到来的时候立刻震动1秒,然后静止1秒,再震动1秒,代码就可以写成:
不过,想要控制手机震动还需要权限声明,因此,我们还得编辑AndroidManifest.xml文件,加入如下声明:
3、接下来我们看一下如何在通知到来时控制手机LED灯的显示:手机基本上都会前置一个LED灯,当有未接电话或未读短信,而此时手机又是处于锁屏状态时,LED灯就会不停的闪烁,提醒用户去查看,我们可以使用:setLights()方法来实现这种效果,setLights()方法接收3个参数,第一个参数用于指定LED灯的颜色,第二个参数用于指定LED灯亮起的时长,以毫秒为单位,第三个参数用于指定LED灯暗去的时长,也是以毫秒为单位,所以,当通知到来的时候,如果想要实现LED灯以绿色的灯光一闪一闪的效果,就可以写成:
如果我们不想进行那么多繁杂的设置,也可以直接使用通知的默认效果,它会根据当前手机的环境来决定播放什么铃声,以及如何震动,写法如下:
注意:以上所涉及的这些进阶技巧都要在手机上运行才能看到效果,模拟器上是无法表现出震动以及LED灯闪烁等功能的。
1.3、通知的高级功能
NotificationCompat.Builder这个类还有很多API我们没有使用过,下面就来学习一些更加强大的API用法,从而构建更加丰富的通知效果:
1、setStyle()方法:这个方法允许我们构建出富文本的通知内容,也就是说通知中不光可以有文字和图标,还可以包含更多的东西。setStyle()方法接收一个NotificationCompat.Style参数,这个参数就是构建具体的富文本信息,如长文字、图片等。
在开始使用:setStyle()方法之前,先做个实验,之前的通知内容都比较短,如果设置成很长的文字是什么效果呢?我们将通知内容写长一点:
现在运行程序,下拉系统状态栏:可以看到通知内容时无法显示完整的,多余部分会用省略号来代替,详细内容放到点击后打开的活动当中显示。
如果需要在通知当中显示一段长文字,Android也是支持的,通过:setStyle()方法就可以做到,具体方法如下:
我们通过setStyle()方法中创建一个NotificationCompat.BigTextStyle对象,这个对象就是用于封装长文字信息的,我们调用它的:bigText()方法并将文字内容传入就可以了。
重新运行程序:
除了显示长文字外,通知还可以显示一张大图片,具体用法也基本相似:
可以看到这里仍然是调用的:setStyle()方法,这次我们在参数中创建了一个NotificationCompat.BigPictureStyle对象,这个对象就是用于设置大图片的,然后调用它的:bigPicture()方法并将图片传入,这里我事先准备好了一张图片,通过BigmapFactory的:decodeResource()方法将图片解析成Bitmap对象,再传入到:bigPicture()方法中就可以了。现在重新运行程序:
2、setPriority()方法:它可以用于设置通知的重要程度,setPriority()方法接收一个整型参数用于设置这条通知的重要程度,一共有5个常量值可选:
PRIORITY_DEFAULT:默认的重要程度,和不设置效果是一样的;
PRIORITY_MIN:最低的重要程度,系统可能只会在特定的场景才会显示这条通知,比如用户下拉状态栏的时候;
PRIORITY_LOW:较低的重要程度,系统可能会将这类通知缩小,或改变其显示的顺序,将其排在更重要的通知之后;
PRIORITY_HIGH:较高的重要程度,系统可能会将这类通知放大,或改变其显示的顺序,将其排在比较靠前的位置
PRIORITY_MAX:最高的重要程度,这类通知消息必须要让用户立即看到,甚至需要用户做出响应操作;
具体的使用方法如下:
这里我们将通知的重要程度设置为了最高,表示这是一条非常重要的通知,要求用户必须立即看到。运行程序后,可以看到这次的通知不是在系统状态栏显示一个小图标了,而是弹出一个横幅,并附带了通知的详细内容,表示这是一条非常重要的通知,不管用户现在是在玩游戏还是在看电影,这条通知都会显示在最上方,以此引起用户的注意,当然,使用这类通知时一定要小心,确保你的通知内容是至关重要的,不然如果让用户产生反感的话,很可能就会导致我们的应用程序被卸载。
二、调用摄像头和相册
我们平时在使用QQ或微信的时候经常要和别人分享图片,这些图片是可以用手机摄像头拍照的,也可以从相册中选取的,几乎在每个应用程序中都会有,这节就学习调用摄像头和相册的知识。
2.1、调用摄像头拍照
先通过一个例子来学习一下如何才能在应用程序里调用手机的摄像头进行拍照:
第一步:新建一个CameraAlbumTest项目,在activity_main.xml文件中设置一个按钮用于打开摄像头进行拍照,再设置一个ImageView用于显示拍出来的照片
第二步:在MainActivity中实现调用摄像头的具体逻辑:
代码分析:
在MainActivity中要做的第一件事自然是分别获取到Button和ImageView的实例,并给Button注册上点击事件,然后在Button的点击事件里开始处理调用摄像头的逻辑:
1、首先创建了一个File对象,用于存放摄像头拍下的图片,这里我们把图片命名为:output_image.jpg,并将它存放在手机SD卡的应用关联缓存目录下。所谓的应用关联缓存目录,就是指SD卡中专门用于存放当前应用缓存数据的位置,调用:getExternalCacheDir()方法可以得到这个目录,具体路径是:/sdcard/Android/data/<package name>/cache。那么为什么要使用应用关联缓存目录来存放图片呢?因为从Android6.0开始,读写SD卡被列为了危险权限,如果将图片存放在SD卡的任何其它目录,都要进行运行时权限处理才行,而使用应用关联目录则可以跳过这一步。
2、接着会进行一个判断,如果运行设备的系统版本低于Android7.0,就调用Uri的fromFile()方法将File对象转换成Uri对象,这个Uri对象标识着output_image.jpg这张图片的本地真实路径。否则,就调用FileProvider的:getUriForFile()方法将File对象转换成一个封装过的Uri对象,getUriForFile()方法接收3个参数,第一个参数要求传入Context对象,第二个参数可以是任意唯一的字符串,第三个参数是刚刚创建的File对象。之所以要进行这样一层转换,是因为从Android7.0系统开始,直接使用本地真实路径的Uri被认为是不安全的,会抛出一个FileUriExposedException异常,而FileProvider则是一种特殊的内容提供器,它使用了和内容提供器类似的机制来对数据进行保护,可以选择性的将封装过的Uri共享给外部,从而提高了应用的安全性。
3、接下来,构建出一个Intent对象,并将这个Intent的action指定为:android.media.action.IMAGE_CAPTURE,再调用Intent的:putExtra()方法指定图片的输出地址,这里填入刚刚得到的Uri对象,最后调用:startActivityForResult()方法来启动活动。由于我们使用的是一个隐式Intent,系统会找到能够响应这个Intent的活动去启动,这样照相机程序就会被打开,拍下的照片将会输出到output_image.jpg中。
4、注意:刚才我们是使用startActivityForResult()来启动活动的,因此拍完照后会有结果返回到:onActivityResult()方法中,如果发现拍照成功,就可以调用BitmapFactory的decodeStream()得到将output_image.jpg这张照片解析成Bitmap对象,然后把它设置到ImageView中显示出来。
第三步:在上面我们使用到了FileProvider内容提供器,所以接下来我们要在AndroidManifest.xml中对这个内容提供器进行注册:
其中,android:name属性的值是固定的,android:authorities属性的值必须要和刚才FileProvider.getUriForFile()方法中的第二个参数一致,另外,这里还在<provider>标签的内容使用<meta-data>来指定Uri的共享路径,并引用一个:@xml/file_paths资源,当然这个资源现在还不存在,下面我们就来创建它。
第四步:右击res目录 ---> New ---> Directory,创建一个xml目录,接着右击xml目录 ---> New ---> File,创建一个file_paths.xml文件,然后修改文件中的内容如下:
其中:external-path就是用来指定Uri共享的,name属性的值可以随便填,path属性的值表示共享的路径,这里设置空值就表示将整个SD卡进行共享,当然也可以仅共享我们存放output_image.jpg这张图片的路径。
第五步:在Android4.4系统之前,访问SD卡的应用关联目录也是要声明权限的,从4.4系统开始不再需要权限声明,为了兼容老版的手机,还需要在AndroidManifest.xml中声明一下访问SD卡的权限:
第六步:运行程序,点击Take Photo按钮,打开相机拍摄,由于可能是尺寸原因,这里拍照完成后并没有在手机主界面显示出来,但是我们查看图片的保存路径:/sdcard/Android/data/<package name>/cache,可以看到我们拍摄的图片以及保存在个位置了。
(1) (2)
(3) (4)
2.2、从相册中选择照片
虽然调用摄像头拍照既方便又快捷,但我们并不是每次都要去当场拍一张照片,因为每个人的手机相册里应该都会存有许许多多张照片,直接从相册里选取一张现有的照片会比打开相机拍一张照片更加常用,一般一个应用程序都应该将这两种选择方式都提供给用户,由用户来决定使用哪一种。下面我们就来看一下,如何才能实现从相册中选择照片的功能。还是在CameraAlbumTest项目的基础上修改:
第一步:在activity_main.xml文件中新增一个用于从相册中选择图片的按钮:
第二步:在MainActivity中加入从相册选择图片的逻辑
代码分析:
1、在Choose From Album按钮的点击事件里先是进行了一个运行时权限处理,动态申请:WRITE_EXTERNAL_STORAGE这个危险权限。因为相册中的照片都是存储在SD卡上的,我们要从SD卡中读取照片就需要申请这个权限,WRITE_EXTERNAL_STORAGE表示同时授予程序对SD卡读和写的能力。
2、当用户授权了权限授权后,就会调用:openAlbum()方法,这里我们先是构建了一个Intent对象,并将它的action指定为:android.intent.action.GET_CONTENT。接着给这个Intent对象设置一些必要的参数,然后调用:startActivityForResult()方法就可以打开相册程序选择照片了,注意在调用:startActivityForResult()方法的时候,我们给第二个参数传入的值变成了:CHOOSE_PHOTO,这样当从相册选择完图片回到:onActivityResult()方法时,就会进入:CHOOSE_PHOTO的case来处理图片。接下来的逻辑就比较复杂了,首先为了兼容新老版本的手机,我们做了一个判断,如果是4.4及以上系统的手机就调用:handleImageOnKitKat()方法来处理图片,否则就调用:handleImageBeforeKitKat()方法来处理图片,之所以要这么做,是因为Android系统从4.4版本开始,选取相册中的图片不再返回图片真实Uri了,而是一个封装过的Uri,因此如果是4.4版本以上的手机就需要对这个Uri进行解析才行。
3、那么:handleImageOnKitKat()方法中的逻辑基本就是如何解析这个封装过的Uri了。这里有好几种判断情况,如果返回的Uri是document类型的话,那就取出document id进行处理,如果不是的话,那就使用普通方式处理。另外,如果Uri的authority是media格式的话,document id还需要再进行一次解析,要通过字符串分割的方式取出后半部分才能得到真正的数字id。取出的id用于构建新的Uri和条件语句,然后把这些值作为参数传入到:getImagePath()方法当中,就可以获取到图片的真实路径了,拿到图片的真实路径后,再调用:displayImage()方法将图片显示在界面上。
4、相比于:handleImageOnKitKat()方法,handleImageBeforeKitKat()方法中的逻辑就要简单得多了,因为它的Uri是没有封装过的,不需要任何的解析,直接将Uri传入到:getImagePath()方法当中,就能获取到图片的真实路径了,最后同样调用:displayImage()方法来让图片显示到界面上。
第三步:运行程序,点击:Choose From Album按钮,首先会弹出权限申请框,点击允许后打卡相册选择一张图片,再回到主界面,我们看到主界面上显示我们选择的图片。
(1) (2)
(3)
在实际的开发过程中,我们更多的是要先对照片进行压缩处理,然后再加载到内存中,压缩图片在网上有很多资源可以学习到。
三、播放多媒体文件
Android在播放音频和视频方面也提供了一套完整的API,使得开发者可以很轻松地编写出一个简易的音频或视频播放器:
3.1、播放音频
在Android中播放音频文件一般都是使用:MediaPlayer类来实现的,它对多种格式的音频文件提供了非常全面的控制方法,从而使得播放音乐的工作变得十分简单。下面列出来MediaPlayer类中一些较为常用的控制方法:
MediaPlayer的工作流程:首先创建出一个MediaPlayer对象,然后调用:setDataSource()方法来设置音频文件的路径,再调用:prepare()方法使MediaPlayer进入到准备状态,接下来调用:start()方法就可以开始播放音频,调用:pause()方法就会暂停播放,调用:reset()就会停止播放。
下面我们通过一个具体的例子来学习:
第一步:新建一个PlayAudioTest项目,然后设置用于开始、暂停、停止音频的按钮:
第二步:在MainActivity中实现功能逻辑
代码分析:
在类初始化的时候先创建了一个MediaPlayer的实例,然后在:onCreate()方法中进行了运行时权限处理,动态申请:WRITE_EXTERNAL_STORAGE权限,这是由于待会儿会在SD卡中放置一个音频文件,程序为了播放这个音频文件必须拥有访问SD卡的权限才行。注意,在:onRequestPermissionsResult()方法中,如果用户拒绝了权限申请,那么就调用:finish()方法将程序直接关掉,因为如果没有SD卡的访问权限,我们这个程序将什么都干不了。
用户同意授权之后,就会调用:initMediaPlayer()方法,为MediaPlayer对象进行初始化操作。在initMediaPlayer()方法中,首先是通过创建一个File对象来指定音频文件的路径,从这里可以看出,我们需要事先在SD卡的根目录下放置一个名为music.mp3的音频文件,后面一次调用:setDataSource()方法和:prepare()方法,为MediaPlayer做好了播放前的准备。
接下来我们看一下各个按钮的点击事件中的代码,当点击Play按钮时会进行判断,如果当前MediaPlayer没有正在播放音频,则调用:start()方法开始播放,当点击Pause按钮时会判断,如果当前MediaPlayer正在播放音频,则调用:pause()方法暂停播放。当点击Stop按钮时会判断,如果当前MediaPlayer正在播放音频,则调用:reset()方法将MediaPlayer重置为刚刚创建的状态,然后重新调用一遍:initMediaPlayer()方法。
最后在:onDestroy()方法中,我们还需要分别调用:stop()方法和:release()方法,将与MediaPlayer相关资源释放掉。
第三步:在AndroidManifest中声明权限:
第四步:允许程序,会先弹出权限申请框,同意授权后,分别点击Play ---> Pause ---> Play ---> Stop
3.2、播放视频
播放视频文件其实并不比播放音频文件复杂,主要是使用Video View类来实现,这个类将视频的显示和控制集于一身,使得我们仅仅借助它就可以完成一个简易的视频播放器。Video View的用法和MediaPlayer也比较类似,主要有以下常用的方法:
接下来我们通过实际的例子来学习一下:
第一步:新建一个PlayVideoTest项目,在布局中设置3个按钮,分别控制视频的播放、暂停、重新播放。按钮下面设置一个VideoView,稍后就将视频展示在这里
第二步:在MainActivity中实现功能逻辑
代码分析:
1、首先在:onCreate()方法中同样进行了一个运行时权限处理,因为视频文件将会放在SD卡上,当用户同意授权后,就会调用:initVideoPath()方法来设置视频文件的路径,这里我们需要事先在SD卡的根目录下放置一个名为:movie.mp4的视频文件。
2、按钮的点击事件:当点击Play按钮时会进行判断,如果当前并没有正在播放视频,则调用start()方法开始播放,当点击Pause按钮时会判断,如果当前视频正在播放,则调用:pause()方法暂停播放,当点击Replay按钮时会判断,如果当前视频正在播放,则会调用resume()方法从头播放视频。
3、最后在onDestroy()方法中,我们还需要调用一下suspend()方法,将VideoView所占用的资源释放掉。
第三步:在AndroidManifest文件中声明用到的权限:
第四步:允许程序,同意授权后点击按钮,注意这里视频最好不要太大。