第十六章 使用Intent拍照
有关FileProvider
FileProvider可以实现从其它应用接受一个文件。需要在androidManifest中定义,例如:
<provider
android:authorities="com.example.a73421.criminalintentchallenge.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/files"/>
</provider>
其中android:name是使用的FileProvider的名称,android:authorities是你定义是一个权限,android:exported表示别的应用是否可以使用这个FileProvider,android:grantUriPermissions表示是否可以授权。
meta-data用于关联路径描述资源,是FileProvider使用的文件地址,其中android:name是这个资源的类型,android:resource是要关联的路径描述资源。xml/files定义如下:
<paths>
<files-path
name="crime_photo"
path="."/>
</paths>
有关相机Intent
依旧是创建一个隐式的intent,操作是
MediaStore.ACTION_IMAGE_CAPTURE
之后因为需要将捕获的照片存到对应路径,还要将文件路径转化为相机应用可识别的uri形式,需要用到FileProvider.getUriForFile()。FileProvider.getUriForFile()中的参数有本地路径,还有使用这个路径所需要的权限(之前在androidManifest中定义的),如:
Uri uri= FileProvider.getUriForFile(getActivity(),"com.example.a73421.criminalintentchallenge.fileprovider",mPhotoFile);
将上面的uri传给相机应用的操作是
MediaStore.EXTRA_OUTPUT 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);
}
从相机应用返回后需要再撤销那个权限:
Uri uri=FileProvider.getUriForFile(getActivity(),"com.example.a73421.criminalintentchallenge.fileprovider",mPhotoFile);
getActivity().revokeUriPermission(uri,Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
有关位图的压缩
需要用到BitmapFactory.options:
public class PictureUtils {
public static Bitmap getScaledBitmap(String path, Activity activity)
{
Point size=new Point();
activity.getWindowManager().getDefaultDisplay().getSize(size);
return getScaledBitmap(path,size.x,size.y);
}
public static Bitmap getScaledBitmap(String path,int destWidth,int destHeight)
{
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFile(path,options);
float srcWidth=options.outWidth;
float srcHeight=options.outHeight;
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;
return BitmapFactory.decodeFile(path,options);
}
}
一些参数说明:
injustDecodeBounds设为true,那么BitmapFactory并不会真的返回一个Bitmap给你,它仅仅会把它的宽,高取回来给你,这样就不会占用太多的内存,也就不会那么频繁的发生OOM。设置为false,BitmapFactory返回bitmap。
outWidth&outHeight是bitmap图像的宽和高;
inSampleSize;获取采样率
inSampleSize大于1时,图像高、宽分别以2的inSampleSize次方分之一缩小
inSampleSize小于等于1时,图像高、宽不变;
所以getScaledBitmap方法干了这么一件事:先把图像的宽和高取回来,分别除一下应用显示区域的宽和高,得到压缩比例,再按照缩放比例新建一个bitmap返回。
有关挑战练习 优化照片显示
做法就和TimePicker过程一样,需要新建一个DialogFragment和相关的.xml文件。
给显示缩略图的mPhotoView增加有关监听器,被按下时创建一个dialog:
mPhotoView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getActivity(),mPhotoFile.toString(),Toast.LENGTH_SHORT).show(); FragmentManager manager=getFragmentManager(); ShowPictureFragment dialog=ShowPictureFragment.newInstance(mPhotoFile); dialog.show(manager,DIALOG_SHOW_PICTURE); } });
其中创建ShowPictureFragment时传入的是要显示的图片的位置。在ShowPictureFragment的onCreateDialog方法中要获取这个图片的位置将其转化为ImageVIew可识别的uri,并且绑定到ImageView中:
public Dialog onCreateDialog(Bundle savedInstanceState) { View v= LayoutInflater.from(getActivity()).inflate(R.layout.dialog_picture_show,null); File file=(File)getArguments().getSerializable(ARG_PICTURE); Uri uri = Uri.fromFile(file); mPictureImageView=(ImageView)v.findViewById(R.id.show_picture); mPictureImageView.setImageURI(uri); return new AlertDialog.Builder(getActivity()). setView(v). setPositiveButton(android.R.string.ok,null). setTitle(R.string.show_picture). create(); }
ImageView有四种指定图片的方法,这里我用的是uri,查看其它用法。
有关挑战练习 优化缩略图加载
这个题目我原以为是优化缩略图的生成效率,实际上是优化缩略图的加载时机,即只有当其布局文件发生变化之后再进行加载。要用到的ViewTreeObserver就可以监听布局的传递。
ViewTreeObserver observer = mPhotoView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mPhotoView.setEnabled(mPhotoFile.exists());
updatePhotoView();
}
});
同样的也可以在里面设置mPhotoView是否可用,当路径不存在照片时就禁用mPhotoView。
ViewTreeObserver的六个接口类的参考。