学习安卓开发[4] - 使用隐式Intent启动短信、联系人、相机应用
在上一篇学习安卓开发[3] - 使用recyclerview显示列表中了解了在进行列表展示时recyclerview的使用,本次记录的是在应用中如何通过隐式intent调用其它应用的功能,比如发短信、打电话、拍照等
- 隐式intent
- 短信
- 判断是否存在相关app
- 相机
- fileprovider
- bitmap
- 功能声明
隐式intent
intent对象用来向操作系统说明需要处理的任务。使用显式intent时,要指定操作系统需要启动的activity,但使用隐式intent,只需告知操作系统想要进行的操作,系统就会启动能完成该操作的activity,如果有多个符合条件的activity,会提供用户一个应用列表供选择
android是如何通过隐式intent找到并启动合适应用的呢?原因在于配置文件中的itent过滤器设置,比如我们也想开发一款短信应用,那么可以在androidmainfest的activity声明中这样设置:
<activity android:name=".crimelistactivity"> <intent-filter> <action android:name="android.intent.action.send" /> <category android:name="android.intent.category.launcher" /> </intent-filter> </activity>
隐式intent的组成部分有
1)要执行的操作,通常以intent类中的常量来表示,比如访问url可以使用intent.action_view,发送邮件使用intent.action_send
2)待访问数据的位置,这可能是设备以外的资源,如某个网页的url,某个文件的uri
3)操作涉及的数据类型,如text/html, audio/mpeg3等
4)可选类别,用来描述对activity的使用方式
短信
那么要启动短信的隐式intent的方法为:
mreportbutton.setonclicklistener(new view.onclicklistener() { @override public void onclick(view view) { intent i = new intent(intent.action_send); i.settype("text/plain"); i.putextra(intent.extra_text, getcrimereport()); i.putextra(intent.extra_subject, getstring(r.string.crime_report_suspect)); i = intent.createchooser(i, getstring(r.string.send_report)); startactivity(i); } });
首先指定发送消息的操作名为action_send,然后消息内容为文本,所以设置数据类型为text/plain,要发送的文本通过extra的形式提供
判断是否存在相关app
使用隐式intent时,如果系统没有安装对应的软件,应用就会奔溃,所以有必要在使用隐式intent时,检查一下能够找到对应的软件,如果没找到,就避免再去发生相关的隐式intent
final intent pickcontact = new intent(intent.action_send); packagemanager packagemanager = getactivity().getpackagemanager(); if (packagemanager.resolveactivity(pickcontact, packagemanager.match_default_only) == null) { mreportbutton.setenabled(false); }
通过packagemanager可以搜索需要的activity的信息,flag标志match_default_only限定只搜索带category_default的activity,如果没有找到,就禁用发短信按钮。
相机
如果所开发的app有拍照功能,就可以使用系统相机了。拍摄的照片要保存在设备文件系统,但这就涉及到私有存储空间的问题。出于安全考虑,无法使用公共外部存储转存,那么如果想共享文件给其他应用,或者接收其他应用的文件(如相机拍摄的照片),可以使用contentprovider把要共享的文件临时暴露出来。对于接受相机拍摄的照片这样的场景,系统提供的现成的fileprovider类。
fileprovider
要使用fileprovider类,需要在androidmainfest中添加声明。
首先添加files.xml文件
<paths> <files-path name="crime_photos" path="."/> </paths>
这个描述性文件把私有存储空间的根路径映射为crime_photos,这个名字仅供fileprovider自己使用。
然后添加fileprovider声明:
<provider android:name="android.support.v4.content.fileprovider" android:authorities="com.example.zhixin.crimeintent.fileprovider" android:exported="false" android:granturipermissions="true"> <meta-data android:name="android.support.file_provider_paths" android:resource="@xml/files" /> </provider>
通过这段声明,提供了一个文件保存地,相机拍摄的照片就可以放在这里了。exported="false"表示除了应用自己和给予授权的应用,其它的不允许使用这个fileprovider,granturipermissions="true"表示允许其他应用向指定文职的uri写入文件。
接下来就可以实现拍照功能了
mphotobutton = (imagebutton) v.findviewbyid(r.id.crime_camera); final intent captureimage = new intent(mediastore.action_image_capture); boolean cantakephoto = mphotofile != null && captureimage.resolveactivity(packagemanager) != null; mphotobutton.setenabled(cantakephoto); mphotobutton.setonclicklistener(new view.onclicklistener() { @override public void onclick(view view) { uri uri = fileprovider.geturiforfile(getactivity(), "com.example.zhixin.crimeintent.fileprovider", mphotofile); captureimage.putextra(mediastore.extra_output, uri); list<resolveinfo> cameraactivities = getactivity().getpackagemanager().queryintentactivities(captureimage, packagemanager.match_default_only); for (resolveinfo activity : cameraactivities) { getactivity().granturipermission(activity.activityinfo.packagename, uri, intent.flag_grant_write_uri_permission); } startactivityforresult(captureimage,request_photo); } }); mphotoview = (imageview) v.findviewbyid(r.id.crime_photo);
通过给所有目标activity授予intent.flag_grant_write_uri_permission权限,允许它们在uri指定的位置写入文件。mphotofile表示拍摄生成照片的名称。
在相机拍摄完成后的回调方法中,取消之前的intent.flag_grant_write_uri_permission授权,并加载显示照片。
uri uri=fileprovider.geturiforfile(getactivity(), "com.example.zhixin.crimeintent.fileprovider", mphotofile); getactivity().revokeuripermission(uri,intent.flag_grant_write_uri_permission); updatephotoview();
bitmap
在显示照片时还有一些工作要做。显示照片要用到bitmap,而bitmap只存储实际像素数据,即使是已经压缩过的照片,存入bitmap后,文件并不会同样压缩,比如一张1600万像素24位的相机照片存为jpg格式约为5mb,但载入bitmap后就会达到48mb左右。
要解决这个问题,需要手动缩放位图照片。首先确认文件大小,然后根据要显示照片的区域大小合理缩放文件,最后重新读取缩放后的文件,再创建bitmap对象。
public class pictureutils { public static bitmap getscaledbitmap(string path, int destwidth, int destheight) { // read in the dimensions of the image on disk bitmapfactory.options options = new bitmapfactory.options(); options.injustdecodebounds = true; bitmapfactory.decodefile(path, options); float srcwidth = options.outwidth; float srcheight = options.outheight; // figure out how much to scale down by int insamplesize = 1; if (srcheight > destheight || srcwidth > destwidth) { float heightscale = srcheight / destheight; float widthscale = srcwidth / destwidth; insamplesize = math.round(heightscale > widthscale ? heightscale : widthscale); } options = new bitmapfactory.options(); options.insamplesize = insamplesize; // read in and create final bitmap return bitmapfactory.decodefile(path, options); } }
还有一个问题是在fragment.oncreateview里面加载照片的时候,无法知道要显示照片的尺寸,只有oncreate, onstart, onresume方法执行过后,才会有首个实例化布局出现。对于这种情况,可以根据fragment所在的activity尺寸确定屏幕的尺寸,按照屏幕尺寸缩放图像。所以再添加一个getscaledbitmap的重载:
public static bitmap getscaledbitmap(string path, activity activity) { point size = new point(); activity.getwindowmanager().getdefaultdisplay() .getsize(size); return getscaledbitmap(path, size.x, size.y); }
最后在oncreateview和相机的回调方法更新照片。
功能声明
既然app需要用到拍照功能,但像拍照、nfc、红外等并不是每个设备都有,所以进行功能声明,从而可以在应用前让用户知道,如果设备缺少某项必须功能,应用商店会拒绝安装应用。
在androidmainfest中添加:
<uses-feature android:name="android.hardware.camera" android:required="false"> </uses-feature>
android:required="false"表示不强制拍照功能,因为如果设备没有相机,会禁掉拍照按钮。
上一篇: 前后端连载笔记