2017年2月份,笔者为了一个项目搞了几天的相册,项目比较急,所以应付了事。昨天突然想起要把这个坑填上,所以“重操旧业”吧。

说到相册,我们首先拥有打开系统文件的权限,其次要获取系统相册中的图片。众所周知,要获取一张图片资源,你没有该图片的“资源路径”是万万行不通的,

在Google Android 4.4 [KitKat] 之前路径返回的相册路径[真实的路径]是这个【content://media/external/images/media/53470】;当Google Android 4.4 [KitKat] 出来

的时候,一种新的相册路径[姑且称为假的路径]是这个[content://com.android.providers.media.documents/document/image%3A48974]  ,也就是说Google 推出的原

生Android路径是两种。

然而中国作为全世界最大的Android 手机厂商国家,基于Android 开放的源代码 ,定制属于自己的一套Android系统 ,为了凸显各自手机系统的优点和特性,加大

市场的竞争优势,一份由Google 给出的 Android 源代码 被改的“面目全非” 。当然,这种现象在互联网发展的历史上又不是没有出现过。比如:OSI [开放系统互联] 当时在

Internet发展早期,致力余研究一种标准协议,以用来屏蔽不同硬件厂商的硬件之间存在差异,提出了分层概念模型和接口的概念,当时TCP/IP算是“义军”突起,强势以

“方便用户[硬件厂商]”的宗旨,抢占市场大量份额,导致OSI这个真正应用于Internet,在这里我们不讨论OSI协议和TCP/IP协议的异同。

由此我们可知道,真正符合用户需求,便利用户才能在当今互联网+物联网时代拔得头筹。

下面我们说说中国各大Android手机厂商之间Android 系统相册返回的图片路径:

Google Android 4.4 之前:content://media/external/images/media/53470

Google Android 4.4 时:content://com.android.providers.media.documents/document/image%3A48974

诺基亚6 Android 7.0 [图库]: content://media/external/images/media/3781

诺基亚6 Android 7.0 [文件管理]:content://com.fihtdc.filemanager.provider/shared_files/ storage/emulated/0/%E8%A1%A8%E6%83%85%E7%8E%8B%E5%9B%BD/
               %E6%89%98%E9%A9%AC%E6%96%AF%E5%B0%8F%E7%81%AB%E8%BD%A6/%E6%89%98%E9%A9%AC%E6%96%

AF%E5% B0%8F%E7%81%AB%E8%BD%A6/ 006uBje5ly1fd3z5c0pb3j306e064q2z_1.jpg

小米 3 :file:///storage/emulated/0/DCIM/Camera/IMG_20160325_233243.jpg

小米 5 Android 7.0 :content://com.miui.gallery.open/raw/%2Fstorage%2Femulated%2F0%2FDCIM%2FCamera%2FIMG_20170707_161721.jpg
content://com.android.fileexplorer.fileprovider/external_files/%E8%A1%A8%E6%83%85%E7%8E%8B%E5%9B%BD/e78319cc

7f0000010128350da8d32327.gif

其他的就不一一列举了,相信路径还是存在差异的,针对不同的路径,我们需要写不同的方法来解析相对相应的路径以获取图片资源。你想一下,

系统相册给你的解析方法就那几个,真的能解析到每一个返回的图片路径吗?不说不同牌子的手机系统存在其他的差异,我只能说这是很不现实的。

我们能不能有一种好的解决方法,统一处理这些存在差异的路径,然后提供一个方法,直接获取图片资源。显然这个想法以来于系统相册是行不通的,

我能想到的是既然系统的相册行不通,我就自己写一个 自定义相册库 ,全局搜索文件系统内的图片[这里的全局搜索文件其实Android系统已经帮我们

做了,其实当你启动Android的时候,手机内部会启动想用的程序帮你检查你手机内部内部的文件,然后以数据库表格的形式存储这些数据信息,包括资源

路径,当然这里面的肯定会有优化算法的,并不是每一次启动Android手机都会全局扫描系统文件吧,不然会严重影响用户体验,这个我们是可以证明的:

当你为一台新手机重装系统,第一回启动手机是,是不是觉得特别慢,更新系统重启是不是是是不是特别慢?想想就知道Android手机系统在进行一些初始

化操作,其中文件检索和数据收集肯定会操作的,只是隐藏掉没有给用户提示罢了]。既然全局搜索文件的工作系统已经帮我们做了,我们就可以直接跳过这

一步骤,我们需要需要做的就是统一图片路径,就像设计模式一书中讲到的适配器模式一样【你把最原始的数据给我,经过适配器处理后,给你想要的数据】。

稍加整理,【搞成SDK,这样就可以在各个项目中用了】。

这样处理之后,这种由系统相册导致图片路径的差异就直接被我们屏蔽掉了。仔细想一想,上面的描述是不是想和有分层概念模型和接口的影子呢?

那么问题来了?自定义相册库行不行的通呢?我记得以前和一位实验室的同学在讨论,QQ和微信是怎么处理的相册,那么神奇,几乎适配所有的Android手机,

直到昨天和一位大神聊天,自己又长知识了。QQ和微信告诉我们,这是行的通的,而且QQ和微信也是这么做的

1:我们先不谈“自定义相册库”,我们看看网上那类雷同的文章,没有突出问题的实质,只是阐述了问题的表象。直接上代码

public class AlbumUtil {/*** 获取文件路径*/@TargetApi(19)public static String getFilePath(Context context, Uri uri ,Intent intent){boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;// DocumentProviderif (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {// ExternalStorageProviderif (isExternalStorageDocument(uri)) {String docId = DocumentsContract.getDocumentId(uri);final String[] split = docId.split(":");final String type = split[0] ;if ("primary".equalsIgnoreCase(type) == true) {return "${Environment.getExternalStorageDirectory()}/${split[1]}" ;}} else if (isDownloadsDocument(uri)) {//DownloadsProviderfinal String id  = DocumentsContract.getDocumentId(uri) ;final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));return getDataColumn(context, contentUri, null, null) ;} else if (isMediaDocument(uri)) {//MediaProviderfinal String docId = DocumentsContract.getDocumentId(uri);final String[] split  = docId.split(":");final String  type = split[0];Uri contentUri = null;if ("image" == type) {contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;} else if ("video" == type) {contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;} else if ("audio" == type) {contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;}final String selection = "_id=?" ;final String[] selectionArgs = new String[] { split[1] };return getDataColumn(context, contentUri, selection, selectionArgs) ;}// MediaProvider// DownloadsProvider} else if ("content".equalsIgnoreCase(uri.getScheme()) == true) {// MediaStore (and general)// Return the remote addressif (isGooglePhotosUri(uri))return uri.getLastPathSegment();return getDataColumn(context, uri, null, null);} else if ("file".equalsIgnoreCase(uri.getScheme()) == true) {// Filereturn uri.getPath() ;}else if (DocumentsContract.isDocumentUri(context, uri) == false){getImagePath(context,uri,intent);}return null ;}/*** Get the value of the data column for this Uri. This is useful for* MediaStore Uris, and other file-based ContentProviders.* @param context The context.* ** @param uri The Uri to query.* ** @param selection (Optional) Filter used in the query.* ** @param selectionArgs (Optional) Selection arguments used in the query.* ** @return The value of the _data column, which is typically a file path.*/private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs){Cursor cursor = null ;final String column = "_data";final String[] projection = { column };try {cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);if (cursor != null && cursor.moveToFirst()) {final int index = cursor.getColumnIndexOrThrow(column);return cursor.getString(index);}if (cursor == null){return uri.getPath();}} finally {if (cursor != null)cursor.close();return null ;}//return null ;}/*** @param uri The Uri to check.* ** @return Whether the Uri authority is ExternalStorageProvider.*/private static boolean isExternalStorageDocument(Uri uri) {return "com.android.externalstorage.documents".equals(uri.getAuthority());}/*** @param uri The Uri to check.* ** @return Whether the Uri authority is DownloadsProvider.*/private static boolean isDownloadsDocument(Uri uri){return "com.android.providers.downloads.documents".equals(uri.getAuthority());}/*** @param uri The Uri to check.* ** @return Whether the Uri authority is MediaProvider.*/private static boolean isMediaDocument(Uri uri){return "com.android.providers.media.documents".equals(uri.getAuthority());}//content://com.android.providers.media.documents/document/image%3A48974/*** @param uri The Uri to check.* ** @return Whether the Uri authority is Google Photos.*/private static boolean isGooglePhotosUri(Uri uri){return "com.google.android.apps.photos.content".equals(uri.getAuthority());}/*** 获取图片路径*/public static String getImagePath(Context context, Uri uri, Intent data){Uri selectedImage = data.getData();//Log.e(TAG, selectedImage.toString());if (selectedImage != null) {String uriStr = selectedImage.toString();String path = uriStr.substring(10, uriStr.length());if (path.startsWith("com.sec.android.gallery3d")) {Log.e("Method selectImage", "It's auto backup pic path:" + selectedImage.toString());return null ;}}final String[] filePathColumn = { MediaStore.Images.Media.DATA };Cursor cursor = context.getContentResolver().query(selectedImage, filePathColumn, null, null, null);String mImgPath ;if (cursor != null){cursor.moveToFirst();int columnIndex = cursor.getColumnIndex(filePathColumn[0]);String picturePath = cursor.getString(columnIndex);mImgPath = picturePath;cursor.close();}else{mImgPath = selectedImage.getPath();}return mImgPath;}}
     //使用intent调用系统提供的相册功能,使用startActivityForResult是为了获取用户选择的图片//只打开图库//Intent getAlbum = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);// getAlbum.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*");// startActivityForResult(getAlbum, IMAGE_CODE);//打开了图库、图片、最近图片Intent intent=new Intent(Intent.ACTION_GET_CONTENT);//ACTION_OPEN_DOCUMENTintent.addCategory(Intent.CATEGORY_OPENABLE);intent.setType("image/jpeg");if(android.os.Build.VERSION.SDK_INT>=android.os.Build.VERSION_CODES.KITKAT){startActivityForResult(intent, SELECT_PIC_KITKAT);}else{startActivityForResult(intent, IMAGE_CODE);}

这里的SELECT_PIC_KITKAT和IMAGE_CODE值可以随便取,他们的作痛主要是在onActivityResult(int requestCode ,int resultCode,Intent intent)方法

里面区分到底调用哪个方法来通过Uri uri  来获得 期望“统一”不存在差异的图片路径资源 ,可惜针对的主体是系统相册,不是自定义的相册库,也只能“哭了”。

protected void onActivityResult(int requestCode, int resultCode, Intent data){//此处的 RESULT_OK 是系统自定义得一个常量,-1表示获取数据成功if (resultCode == -1){Bitmap bitmap = null;//外界的程序访问ContentProvider所提供数据 可以通过ContentResolver接口ContentResolver resolver = getContentResolver();//此处的用于判断接收的Activity是不是你想要的那个,IMAGE_CODE为自定义的if (requestCode == IMAGE_CODE || requestCode==SELECT_PIC_KITKAT) {Uri uri = data.getData();System.out.println("phone's path:"+uri.toString());String path = "" ;if (requestCode == IMAGE_CODE) {path = SDCardDataPickUtil.getFilePath(AlbumActivity.this, uri, data);}else if (requestCode==SELECT_PIC_KITKAT) {path = SDCardDataPickUtil.getFilePath(AlbumActivity.this, uri, data);if (path == null || "".equals(path))path = SDCardDataPickUtil.getImagePath(AlbumActivity.this, uri, data);}mPath = path ;editor.putString("path",path);editor.commit();imgPath.setText(path);imgShow.setImageBitmap(loadFromSdCard(path));}}else{return;}}

上面这几段段代码,相信你在网上看到太多次了,导致不知道原版是谁[我听说是有人从国外翻译+自己的理解]?我个人观点是第一个吃螃蟹的人别人会夸他勇气可嘉,

第二个乃至以后吃螃蟹的人就不会有人这么夸了,除非有人提出一些独特见解,才有可能收到部分人的夸奖。

2、我们谈一谈 自定义相册库 , 大家可参考这篇文章,我99%还是出自这篇文章 。Android 自定义本地图片加载库,仿微信相册

下面是真片文章作者的思路:

总结一下微信的本地图片加载有以下几个特点,也是提高用户体验的关键点
1、缩略图挨个加载,一个一个加载完毕,直到屏幕所有缩略图都加载完成
2、不等当前屏的所有缩略图加载完,迅速向下滑,滑动停止时立即加载停止页面的图片
3、已经加载成功的缩略图,不管滑出去多远,滑回来的时候不需要重新加载
4、在相册以外的环境中,需要让imageView的宽高比例随图片的宽高比例自动伸缩,而且要在图片加载完毕之前就要预留占位空间

为了满足上面几个要求,主要采用以下几个方法:
0、为了防止图片加载出来OOM,需要对分辨率和颜色的位数进行缩小到合适范围,同时采用LRU [操作胸痛中一种页面置换算法:最近最久为使用] 缓存
1、采用一个定长线程池,线程池的大小等于CPU的数量+1,把所有缩略图加载任务都交给线程池执行,以获得最快的加载效率。
2、在用户快速滑动的时候,没有加载完毕的划走了的图片立即停止加载,将所占线程让出来,让新的加载任务执行。
3、已经加载成功的缩略图,保存到sd卡中,下次再滑动回来的时候,直接从sd卡加载以前保存好的小图,不经过线程池。
4、对于三星这样的手机,其图片全都是宽度大于高度,方向用exif进行记录,图片加载器要读出exif的方向信息,然后通过矩阵进行旋转

代码我就不着办过来了,大家有兴趣可以点击“Android 自定义本地图片相册库,仿微信

这是该文章在Github上的源代码链接:https://github.com/AlexZhuo/AlxImageLoader

最后笔者在重点说一下:

既然我们的程序要和相同文件程序进行数据交互,Android给我们提供了一个强大的组件 “内容提供器” ,不了解的读者可以取百度,这里笔者只做简要的概述。

内容提供器(Content Provider)主要用于在不同应用程序之间实现数据共享的功能,他提供那个了一套完整的机制,允许一个程序访问另外一个程序中的数据,

同时还能保证被访问数据的安全性。

用法有两种:1、使用现有的内容给提供器来读取和操作相应程序中的数据【Android自带的内容提供器:电话簿、短信、媒体、日历等程序】

2、创建自己的内容提供器给我们的应用程序的数据提供外部访问接口

具体的介绍请看官网:https://developer.android.com/guide/topics/providers/content-providers.html 【应该需要翻墙吧】

读者需要了解的是:ContentResolver 的基本用法[【https://developer.android.com/reference/android/content/ContentResolver.html】 和ContentResolver自带的增删改查方法,笔者认为这篇文章对ContenTResolver的查询方法介绍比较详细   Android 学习笔记 Contacts (一)ContentResolver query 参数详解

如果读者对 projection 【投影】词语感到困惑的话,可以取看下数据库相关的数据,比如【数据库系统概论 第五版】,书中对这个词语有非常详细的描述。

下面是 自定义相册库  最重要的代码:

    /*** 从系统相册里面取出图片的uri*/public static void get500PhotoFromLocalStorage(final Context context, final LookUpPhotosCallback completeCallback) {new AlxMultiTask<Void,Void,ArrayList<SelectPhotoEntity>>(){@Overrideprotected ArrayList<SelectPhotoEntity> doInBackground(Void... params) {ArrayList<SelectPhotoEntity> allPhotoArrayList = new ArrayList<>();Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;ContentResolver mContentResolver = context.getContentResolver();//得到内容处理者实例String sortOrder = MediaStore.Images.Media.DATE_MODIFIED + " desc";//设置拍摄日期为倒序Log.i("Alex","准备查找图片");// 只查询jpeg和png的图片Cursor mCursor = mContentResolver.query(mImageUri, new String[]{MediaStore.Images.Media.DATA},MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?",new String[]{"image/jpeg", "image/png"}, sortOrder+" limit 500");if (mCursor == null) return allPhotoArrayList;int size = mCursor.getCount();Log.i("Alex","查到的size是"+size);if (size == 0) return allPhotoArrayList;for (int i = 0; i < size; i++) {//遍历全部图片mCursor.moveToPosition(i);String path = mCursor.getString(0);// 获取图片的路径SelectPhotoEntity entity = new SelectPhotoEntity();entity.url = path;//将图片的uri放到对象里去allPhotoArrayList.add(entity);}mCursor.close();return allPhotoArrayList;}

没错,这个方法是从 系统相册中 取出图片的URL,此时的URL格式是没有多大差异的。看到这里读者可能会有疑惑,这不是瞎扯嘛,请注意,这里的得到的路径是

真是的路径,都是这种路径【/storage/emulated/***/***/文件名.后缀名】,而手机系统自带相册最初资源的路径也是种格式,只不过是当当我们选中图片后

返回给我们的资源的路径不同,导致存在很大差异,就如文章刚开头提到的哪集中路径。所以我们 自定义相册库 的目的在意统一资源路径,说白了就是保持最原始的资

源路径[/storage/emulated] ,其实 自定义相册库 就相当余一个 中间件 ,确保存储区的资源通过原始路径能提供给用户[这里的用户指的是开发人员]使用,就不比再为

资源路径不统一的差异而苦恼了。

弃掉Android 4.4获取系统图片出错之坑,实现 自定义相册库相关推荐

  1. 仿抖音短视频系统源码,获取系统图片

    仿抖音短视频系统源码,实现获取系统图片的相关代码如下: 首先开权限 <uses-permission android:name="android.permission.WRITE_EX ...

  2. android uri图片压缩,详解android 通过uri获取bitmap图片并压缩

    详解android 通过uri获取bitmap图片并压缩 很多人在调用图库选择图片时会在onactivityresult中用media.getbitmap来获取返回的图片,如下: uri mimage ...

  3. Android第三方app获取系统属性

    原博客地址(建议去原博客地址看): https://blog.csdn.net/shadowliucs/article/details/38658155 在一个Android应用中因为要获取系统的属性 ...

  4. 获取系统时间出错oracle-,oracle 获取系统时间(转)

    Oracle中如何获取系统当前时间 select to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual; ORACLE里获取一个时间的年.季.月.周.日 ...

  5. Android 利用url获取Bitmap图片

    编程中我们经常需要通过url获取网络上的图片.下面将相关代码做一下总结 注意:Android中这一操作必须在异步线程中进行 public class BitmapUtil {public static ...

  6. 如何使Android应用程序获取系统权限来修改系统时间

    在 android 的API中有提供 SystemClock.setCurrentTimeMillis()函数来修改系统时间,可惜无论你怎么调用这个函数都是没用的,无论模拟器还是真机,在logcat中 ...

  7. android xutils3.0获取服务器图片,Android端使用xUtils3.0实现文件上传

    privatevoidupLoadOnClick(View v) {        String upUrl ="/mnt/sdcard/pic/test.jpg";//指定要上传 ...

  8. android开发炫酷背景图片,12 个炫酷背景特效库

    作者:lindelof 译者:前端小智 来源:github点赞再看,微信搜索GitHub https://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文 ...

  9. android 图片 写入文件格式,怎么把web服务器端获取的图片写入android客户端文件...

    怎么把web服务器端数据库获取的图片写入android客户端文件 解决方案 10 获得图片,然后用FileoutputStream写入到文件-- 10 先用htturlconnection(或其他方法 ...

最新文章

  1. python入门第一课练习题_老男孩教育Python 9期第一课练习题答案
  2. 用 Flask 来写个轻博客 (34) — 使用 Flask-RESTful 来构建 RESTful API 之三
  3. SQL Server中的角色(服务器级别和数据库级别角色)
  4. 做CEO,一定要有“江湖感”
  5. 信息检索报告_读者信息素养状况问卷调查分析报告来啦
  6. linux查看证书位数,查看Linux系统是32位还是64位(getconf WORD_BIT误区)
  7. Java黑皮书课后题第1章:1.13(代数:求解2*2线性方程组)编写程序,求解以下方程组并显示x和y的值 3.4x+50.2y=44.5 2.1x+0.55y=5.9
  8. android多音字排序,Android拼音排序
  9. SAP MIGO的界面中Document Overview部分,可以不让一些单据在这里显示。
  10. 光电转换器有什么作用?光纤收发器如何保养?
  11. StarkSoft题库管理系统
  12. android 开源框架
  13. MyEclipse2014+JDK1.7+Tomcat8.0+Maven3.2 开发环境搭建
  14. 安装软件Mimics过程记录
  15. 石家庄规划建设智慧城市 三年后城市将大变样
  16. 【RFID】天线技术
  17. 7、8系升9系视频教程
  18. 德国奔驰、博世和保时捷的员工年薪有多少?
  19. 软考英文缩写_计算机软件常见英文缩写及对应全称
  20. HCIA-Intelligent Computing 题目汇总

热门文章

  1. P1717 钓鱼 洛谷
  2. Android 签名证书生成及jks与keystore 相互转换
  3. Web:选择器的种类
  4. 计算机工程与设计 北大核心,计算机工程与设计 统计源期刊北大核心期刊
  5. WebApp学院助手研发总结
  6. 史上最强人工智能ChatGPT 到底有多强?
  7. Beautiful Soup 中文文档
  8. QH VASP 命令
  9. 关于elementui的table主子表展示数据
  10. 第十二届蓝桥杯(2021年)模拟赛 Python组(第一期) 题目+个人解答