android圖片系統解決方案-從採集到顯示

概述

Android上圖片涉及到的要點:html

  • 基礎
  • 自定義相機拍照
  • 調用系統相機拍照
  • 圖片選擇
  • 裁剪
  • 壓縮
  • 上傳
  • 服務端處理與下載
  • 顯示與內存-普通圖與超大圖
  • 文件夾管理
  • 內置圖片管理與包大小

基礎

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兼容性問題最大的地方,沒有之一.因此,不要做死去用原生的,或者本身從頭開始寫一套. 直接用開源的.github

CameraView算法

CameraView-guide數據庫

固然,原生api的使用仍是要掌握的,有幾個比較好的博客供參考: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文件夾.
相關文章
相關標籤/搜索