android 使用相机拍照以及FileProvider源码浅析
来源:互联网 发布:php跟java的区别 商城 编辑:程序博客网 时间:2024/06/07 17:29
调用系统相机拍照有两种方式:
ONE: 使用Intent传递Bitmap
button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Intent intent=new Intent("android.media.action.IMAGE_CAPTURE"); startActivityForResult(intent,1); }
为一个button 设置点击事件,隐式intent的action为"android.media.action.IMAGE_CAPTURE"
同样应该为其设置回调方法。。
protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode,resultCode,data); if(resultCode==RESULT_OK){ if(requestCode==1){ Bundle bundler=data.getExtras(); Bitmap bitmap=(Bitmap) bundler.get("data"); iamgeview.setImageBitmap(bitmap); //自定义一个ImageView接受Bitmap } } }
相机的Activity会将Bitmap对象序列化后存放Bundler里 。大概代码:
Bundle bundle=new Bundle(); bundle.putParcelable("data", object); //object即是序列化后的Bitmap对象 intent.putExtras(bundle);
这里不需要权限,因为我们只是通过intent打开相机的activity而打开相机的activity并没有权限。 这种方法得到的Bitmap只是缩略图,画质非常的差,android做了相关处理防止内存泄露。 所以一般我们用的是第二种。
TWO: 使用Intent传递URI:
二者不同的是前者直接在两个Activity之间传递Bitmap,而后者是发送一个URI给相机的Acitivity后让其把Bitmap数据直接保存到URI对应的文件里,
然后我们的app再直接访问这个文件,这样就不存在内存泄露了。
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { File file=new File(getExternalCacheDir().getPath(),"Camera"); if (file.exists()){ file.delete(); } try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } Uri image_uri=Uri.fromFile(file); Intent intent=new Intent("android.media.action.IMAGE_CAPTURE"); intent.putExtra(MediaStore.EXTRA_OUTPUT,image_uri); startActivityForResult(intent,1); }});getExternalCacheDir() // 得到系统给app分配的外置存储空间文件夹:0/Android/data/包名/cache我们在这里创建了一个名为Camera的文件。然后通过Uri.fromFile(file)得到文件的uri,然后将其存放在一个"key"为MediaStore.EXTRA_OUTPUT 这个必须是固定格式不然相机Activity得不到正确的数据。 这个key的本身的值为"output"。继续修改回调方法。protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode,resultCode,data); if(resultCode==RESULT_OK){ if(requestCode==1){ File file=new File(getExternalCacheDir(),"Camera"); if(file.exists()){ try { imageview.setImageBitmap(BitmapFactory.decodeStream(new FileInputStream(file))); } catch (FileNotFoundException e) { } } } } }将这个file文件读取后放到imageview中,然后就ok了。为什么这里任然要执行onActivityResult 应为我们发起的intent目标Activity本身就是一个最终会执行setResult(),然后finish()。
不同上述的是 前者无intent.putExtra(MediaStore.EXTRA_OUTPUT,image_uri);所以猜想源码肯定是会读取这个值,如果读不到则把Bitmap数据包装成intent
发回来,能读到则把Bitmap数据存放到uri对应的File文件,而intent的返回值为null。
注意: intent传输uri方法在sdk版本低于24也就是android7.0时使用,而对于7.0及以上我们则需要用到内容提供器(FileProvider),详细阅读这里:http://blog.csdn.net/djy1992/article/details/72533310
为什么出现这种问题,其实就是Uri.fromFile(file)方法不让用了,我们不能从File转换Uri了,所以需要FileProvider。从继承关系看FileProvider是继承ContentProvider所以也需要在Mainfest中定义public class FileProvider extends ContentProvider<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.android.shuai" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file"></meta-data></provider>
name:为定值"android.support.v4.content.FileProvider"。相当自定义的ContentProvider,FileProvider为重写的类。
authorities:自定义值,<meta-data>标签内的name:为定值,android:exported="false": 不可更改否则报错 android:grantUriPermissions="true"> :不可更改否则报错
resource:新建XML目录新建XML文件
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="LinLangTianXia" path="" /> </paths>
<external-path> : 首地址,通俗讲就是地址从这里开始。
path="" : 首地址后的详细地址:
臂如标签就是上述的<external-path> ,path=""; 那么我们的内容提供器可以提供的转换File地址为:
"/Storage/"。只要要转换的File文件被这个目录包括就可以。如果path="A" 那就是"/Storage/A/"。
同理<external-path>标签还有很多;像<files-path><cache-path>这种,如果要转换的File文件不在上述内就会报错。
name则是上述地址的映射,比如上述"/Storage/A/" name="LinLangTianXia" 最后为/LinLangTianXia/, 也就是说将我们的真实地址隐藏了。
举个例子 :7.0以下得到的Uri为:file:///storage/emulated/0/Android/data/listen.myapplication/cache/Camera:
7.0以上得到的Uri为: content://com.android.shuai/LinLangTianXia/Android/data/listen.myapplication/cache/Camera:
格式为:content://authorities/name/+未映射的地址。 authorities告诉程序是那个内容提供者, name映射真实地址。
当然这个转换过程不用我们来操作看代码:if(Build.VERSION.SDK_INT>=24){ image_uri=FileProvider.getUriForFile(Main.this,"com.android.shuai",file); }
调用一下FileProvider.getUriForFile(Context,authorities,file)就ok了, 这个方法会自动帮我们封装Uri,这个file就是上述我们说的file
必须在其内部,否则报错。 其他不变同7.0以下下, 通俗讲FileProvider只是帮我们修改了Uri使其更安全而已。 当然阅读到这会有个疑问它们具体的过程到底是怎样的,我们看源码:public static Uri getUriForFile(Context context, String authority, File file) { final PathStrategy strategy = getPathStrategy(context, authority); return strategy.getUriForFile(file); //看到strategy对象可以帮我们转换File为Uri }
进入PathStrategy发现是个接口实现它的是类SimplePathStrategy,继续进:static class SimplePathStrategy implements PathStrategy { private final String mAuthority; private final HashMap<String, File> mRoots = new HashMap<String, File>(); public SimplePathStrategy(String authority) { mAuthority = authority; //传入的authority。 } @Override public Uri getUriForFile(File file) { String path; try { path = file.getCanonicalPath(); //相当file.getPath(); } catch (IOException e) { throw new IllegalArgumentException("Failed to resolve canonical path for " + file); } // Find the most-specific root path Map.Entry<String, File> mostSpecific = null; for (Map.Entry<String, File> root : mRoots.entrySet()) { final String rootPath = root.getValue().getPath(); if (path.startsWith(rootPath) && (mostSpecific == null || rootPath.length() > mostSpecific.getValue().getPath().length())) { mostSpecific = root; } } if (mostSpecific == null) { throw new IllegalArgumentException( "Failed to find configured root that contains " + path); } // Start at first char of path under root final String rootPath = mostSpecific.getValue().getPath(); if (rootPath.endsWith("/")) { path = path.substring(rootPath.length()); } else { path = path.substring(rootPath.length() + 1); } // Encode the tag and path separately path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/"); return new Uri.Builder().scheme("content") .authority(mAuthority).encodedPath(path).build(); }} //截取部分
看这行: return new Uri.Builder().scheme("content") .authority(mAuthority).encodedPath(path).build(); 是不是就是我们上述的结果。看到相信你肯定会有疑惑,既然Uri被这种方式修改那在接收端会不会存在另一种方式给它修改回去,没错,接着看剩下源码:class SimplePathStrategy {private final String mAuthority;private final HashMap<String, File> mRoots = new HashMap<String, File>();public SimplePathStrategy(String authority) {mAuthority = authority;}/*** Add a mapping from a name to a filesystem root. The provider only offers* access to files that live under configured roots.*/public void addRoot(String name, File root) {if (TextUtils.isEmpty(name)) {throw new IllegalArgumentException("Name must not be empty");}try {// Resolve to canonical path to keep path checking fastroot = root.getCanonicalFile();} catch (IOException e) {throw new IllegalArgumentException("Failed to resolve canonical path for " + root, e);}mRoots.put(name, root);}public File getFileForUri(Uri uri) {String path = uri.getEncodedPath();final int splitIndex = path.indexOf('/', 1);final String tag = Uri.decode(path.substring(1, splitIndex));path = Uri.decode(path.substring(splitIndex + 1));final File root = mRoots.get(tag);if (root == null) {throw new IllegalArgumentException("Unable to find configured root for " + uri);}File file = new File(root, path);try {file = file.getCanonicalFile();} catch (IOException e) {throw new IllegalArgumentException("Failed to resolve canonical path for " + file);}if (!file.getPath().startsWith(root.getPath())) {throw new SecurityException("Resolved path jumped beyond configured root");}return file}}看到这些代码是不是有点头疼,别急,慢慢来: 笔者将其提取然后直接调用getFileForUri报错提示root为空,所以上述的addRoot要先调用,那这个addroot方法又在那里调用呢?public SimplePathStrategy parsePathStrategy(Context context, String authority) throws IOException, XmlPullParserException { final SimplePathStrategy strat = new SimplePathStrategy(authority); //这里创建了SimplePathStrategy对象 final ProviderInfo info = context.getPackageManager() .resolveContentProvider(authority, PackageManager.GET_META_DATA); final XmlResourceParser in = info.loadXmlMetaData( context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS); if (in == null) { throw new IllegalArgumentException( "Missing " + META_DATA_FILE_PROVIDER_PATHS + " meta-data"); } int type; while ((type = in.next()) != END_DOCUMENT) { if (type == START_TAG) { final String tag = in.getName(); final String name = in.getAttributeValue(null, ATTR_NAME); String path = in.getAttributeValue(null, ATTR_PATH); File target = null; if (TAG_ROOT_PATH.equals(tag)) { target = DEVICE_ROOT; } else if (TAG_FILES_PATH.equals(tag)) { target = context.getFilesDir(); } else if (TAG_CACHE_PATH.equals(tag)) { target = context.getCacheDir(); } else if (TAG_EXTERNAL.equals(tag)) { target = Environment.getExternalStorageDirectory(); } else if (TAG_EXTERNAL_FILES.equals(tag)) { File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null); if (externalFilesDirs.length > 0) { target = externalFilesDirs[0]; } } else if (TAG_EXTERNAL_CACHE.equals(tag)) { File[] externalCacheDirs = ContextCompat.getExternalCacheDirs(context); if (externalCacheDirs.length > 0) { target = externalCacheDirs[0]; } } if (target != null) { strat.addRoot(name, buildPath(target, path)); //这里调用了addRoot方法(); } } } return strat;}ok 我们的逻辑也算梳理顺了, 在相机源码中肯定先调用了parsePathStrategy()方法得到了SimplePathStrategy对象,然后直接调用了它的getFileForUri()方法于是就将Uri给改了回来。 笔者熬夜到两点了, 水平有限源码分析的很浅
,但是看在笔者努力的份上 给点支持,谢谢!!!
阅读全文
0 0
- android 使用相机拍照以及FileProvider源码浅析
- Android调用相机应用拍照及FileProvider使用
- android相机拍照源码
- android 拍照使用fileprovider遇到的坑
- 使用android内置相机拍照
- Android 7.0拍照/相册/截取图片FileProvider使用
- Android 7.0拍照/相册/截取图片FileProvider使用
- Android FileProvider的使用
- 【Android】Android相机拍照
- android相机拍照
- Android中相机拍照
- Android相机拍照,储存
- android相机camera拍照
- android打开相机拍照
- android自定义相机拍照
- 79使用相机拍照
- 使用相机实现拍照
- Android 调用系统相机以及相册源码
- openssl对数组加密解密的完整实现代码
- 722. Remove Comments
- php 登录后返回上一页面
- spring boot 中 Mybatis plus 多数据源的配置
- vim 批量重载缓冲区
- android 使用相机拍照以及FileProvider源码浅析
- 数据结构期末复习:排序算法
- jquery下的ajax详解
- 直播SDK加入混响效果,创造演唱会现场沉浸感音效
- Git开源三方库
- 深入了解ThreadLocal
- Intellij IDEA的使用
- **决策树基础以及Python代码实现**
- vue 动态加载图片src的解决办法