android图片系统解决方案-从采集到显示

概述

Android上图片涉及到的要点:

  • 基础
  • 自定义相机拍照
  • 调用系统相机拍照
  • 图片选择
  • 裁剪
  • 压缩
  • 上传
  • 服务端处理与下载
  • 显示与内存-普通图与超大图
  • 文件夹管理
  • 内置图片管理与包大小

基础

Android-Exif-Extended示例:
    public native static boolean nativeCompress(Bitmap bitmap,int quality,String outPath);
    
    public static boolean compressOringinal(String srcPath,int quality,String outPath){
        Bitmap bitmap = BitmapFactory.decodeFile(srcPath);
        boolean success =  nativeCompress(bitmap,quality,outPath);//libjpeg压缩
        if(success){
            ExifInterface exif = new ExifInterface();
            try {
                exif.readExif( srcPath, ExifInterface.Options.OPTION_ALL );
                exif.writeExif( outPath );//全部exif信息拷到新文件那儿去
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                return true;
            }
        }
        return success;
    }
复制代码

自定义相机拍照

camera api是Android兼容性问题最大的地方,没有之一.所以,不要作死去用原生的,或者自己从头开始写一套. 直接用开源的.

CameraView

CameraView-guide

当然,原生api的使用还是要掌握的,有几个比较好的博客供参考:

Android相机开发系列:很全面

Android多媒体之Camera的相关操作:相当于一个概述

注意点:

  • preview和takepicture两套不同的操作,不同的supportedSize. size的选择需要与预览的surfaceview相配合,避免拉伸.
  • 前置摄像头的takepicture的自拍镜像问题
  • 图像矩阵横屏90°.方向旋转可以用纯java数组转换,也可以用opencv 的api,或者renderscript,或者利用bitmap中转,性能最好的是renderscript.
  • 最终图像质量:如果对图片质量(清晰度,亮度)有所要求,可以使用preview的api来采集多帧,取评价最高的一帧作为最终的结果. 评价标准方面,可以基于opencv开发一套对清晰度和亮度判断的算法. 更牛的就是多帧合成,顶级实践就是谷歌,华为,小米的超级夜景算法.

调用系统相机拍照

注意兼容性:

  • 构建intent时指定文件保存位置,避免有些机型拿不到默认存储位置的返回
  • Uri 在7.0以上系统的兼容
  • 8.0以上系统,即使是调用系统相机拍照,也需要请求权限,否则crash

标准的intent

 Intent takeIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 Uri photoUri = getMediaFileUri(TYPE_TAKE_PHOTO);
 takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
 startActivityForResult(takeIntent, CODE_TAKE_PHOTO);
复制代码

Uri兼容性

intent读写权限都加上,不管任何版本:

调用系统拍照,Intent需要加上读和写权限. 如果没有写权限,部分4.4以下手机会crash:
```
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

或者这种实现:
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
```
复制代码

7.0以上兼容性的实现

抄官方demo谁都会,但后果就是导致容易跟第三方库内部实现冲突. 这里的重点在于与其他已内部实现Uri兼容性的第三方库的兼容:

//官方demo:原封不动搬过来,就可能会有跟第三方库冲突.因为别人一般也是原封不动搬过来的
<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="${applicationId}.fileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>

//正确示范:

<provider
            android:name="org.devio.takephoto.permission.TFileProvider"//name要自己继承v4里的fileprovider
            android:authorities="${applicationId}.takephoto.fileprovider"//自己附加字符串,后面要提供解析方法
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths_takephoto" />//记得加后缀.尤其作为库提供时,防止被同名覆盖.
</provider>

//xml里: 
<paths>
<!--外置SD卡-->
        <root-path
            path=""
            name="camera_photos" />
//如果是库,一般只提供上面一个就够了,下面的由app自己的module去提供.
<!--Context.getFilesDir()-->
        <files-path
            name="files"
            path=""/>
<!--Context.getCacheDir()-->
        <cache-path
            name="cache"
            path=""/>
 <!--Environment.getExternalStorageDirectory()-->
        <external-path
            name="external"
            path=""/>
<!--Context.getExternalFilesDir(null)-->
        <external-files-path
            name="external_file_path"
            path=""/>
<!--Context.getExternalCacheDir()--> <!--直到support-v4:25.0.0才支持-->
        <external-cache-path
            name="external_cache_path"
            path=""/>
    </paths>
    
//然后提供解析这个uri的方式(sd卡):

public static String parseOwnUri(Context context, Uri uri) {
        if (uri == null) {
            return null;
        }
        String path;
        if (TextUtils.equals(uri.getAuthority(), context.getPackageName() + ".takephoto.fileprovider") {
            path = new File(uri.getPath().replace("camera_photos/", "")).getAbsolutePath();
        } else {
            path = uri.getPath();
        }
        return path;
}
   
复制代码

规避

本质上还是因为从7.0开始强制执行sctrickmode,也可以写代码让它不执行:只是不建议这样做罢了.

/过滤URI检查
        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
        StrictMode.setVmPolicy(builder.build());

复制代码

图片选择

可以调用系统intent去图库里选一张图,但是不同手机的图库选择千差万别,返回的uri依据手机厂商和版本的不同而情况不同,有的是contentprovider形式,有的是file uri形式,Android 7.0以上以下还不一致. 另外,系统intent不支持多选. 搞来搞去,不如自己查media center数据库,自己做ui. 基本上成熟的app都是通过这种形式实现,网上开源库也一大把.

github.com/zhihu/Matis…

github.com/donglua/Pho…

图片裁剪

有系统intent,但是不同手机千差万别,大多数很差劲.果断用开源的. 开源库中,ucrop秒杀其他裁剪库: github.com/Yalantis/uC…

当然,也仍有不足之处: 选择区域后裁剪该区域,不是基于原图来裁,而是基于界面上预览图来裁剪,所以会自带压缩效果.

图片压缩

  • 尺寸压缩与质量压缩:

对于手机图片来说,80%质量和100%质量并无肉眼可见的明显区别,而文件大小相差近一倍.完全没有必要设为100%的质量,而如果是相机拍摄的原图而非进行尺寸压缩后的图,70%的质量已经足够.

所以,一般来说: 经过尺寸压缩的图,可设置质量80%. 原图大小,可将质量设置为70%. 两者都可用下面的libjpeg来压缩.

  • libjpeg-turbo库在Android上的压缩优化

Bitmap.compress->skia->libjpeg->file,file->libjpeg->skia->bitmap 所有的问题都可以从这里找到答案.

  • 同样分辨率的图片,Android图片文件比ios大: Android上skia库调用libjpeg时使用的是霍夫曼定长编码而非动态的霍夫曼编码.
  • 多次压缩的图片发绿问题: Android skia库的bug: RGB转YUV时是int转float操作,google工程师为了加快运算速度在进行了位移操作,会导致细微精度丢失,多次压缩,解压再压缩操作会导致图片渐渐发绿. 参考
  • inSampleSize 只接受2的n次幂: 因为libjpeg解压时就只支持这样的传入

参考:www.voidcn.com/article/p-w…

Android图片压缩终结篇

彩蛋: 自己写的图片压缩工具app

  • 模仿微信压缩效果的库: Luban

拍照/图片选择-图片裁剪-压缩 的整合

github.com/crazycodebo…

github.com/LuckSiege/P…

结合app业务的二次封装技巧: 透明fragment接收onactivityResult,达到最终静态方法+回调的一行代码调用的效果:

        TakePhotoUtil.startPickOne(fragmentActivity, isForCamera, new TakeOnePhotoListener() {
           @Override
           public void onSuccess(String usableFilePath) {
               showImg(usableFilePath);
           }

           @Override
           public void onFail(String filePath, String msg) {
               MyToast.errorBigL(msg);
               //toTakePhotoMode();
           }

           @Override
           public void onCancel() {
              // toTakePhotoMode();
           }
       });

复制代码

ps. 透明fragment的封装技巧参考: RxPermissionsFragment

图片上传

  • 控制图片源,尽量小
  • 多图并发,结合rxjava
  • 顶层封装,api友好

服务端处理与下载

  • CDN缓存
  • 提供服务端处理能力:resize,裁剪,模糊.七牛和阿里云的图片存储服务均提供了此类功能.客户端通过url后添加参数,获得比原图小的图片,节省流量.

示例: 78re52.com1.z0.glb.clouddn.com/resource/go…

代码示例:QiniuUtils

客户端显示

文件夹管理

  • 项目中提供统一的获取方法,删除方法
  • cache使用的注意事项:存储不足时会被系统优先清空

内置图片管理与包大小

  • tinypng/tinyjpg先压缩一遍
  • 用不到透明通道的png统一转换成jpg
  • 大一些的图,可以放到服务端,首次进入app时下载到files文件夹.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值