Android 圖片保存,相冊刷新(版本兼容)

Android 常見功能保存圖片十分常用,近年來隨著Android版本更新,逐漸收緊了App的權(quán)限,導(dǎo)致App存儲圖片需要做的兼容性問題越來越多.

原因:

  • 廠商定制存儲方式
  • 不同版本存儲方式不一致
  • Android Q 沙盒機制

導(dǎo)致的問題:

  • 文件存儲異常
  • 相冊不展示下載的圖片
  • 相冊展示重復(fù)的下載圖片

Android Q (10) 新增了分區(qū)存儲
針對外部存儲的過濾視圖靠抑,可提供對特定于應(yīng)用的文件和媒體集合的訪問權(quán)限,所以圖片保存的時候需要存儲到指定App文件夾下才能保存文件

兼容實現(xiàn):

1. 處理Android Q 存儲地址問題

  /**
     * 根據(jù) Android Q 區(qū)分地址
     *
     * @param context
     * @return
     */
    public static String getPath(Context context) {
        // equalsIgnoreCase() 忽略大小寫
        String fileName = "";
        if (Build.VERSION.SDK_INT >= 29) {
            fileName = context.getExternalFilesDir("").getAbsolutePath() + "/current/";
        } else {
            if ("Xiaomi".equalsIgnoreCase(Build.BRAND)) { // 小米手機
                fileName = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/";
            } else if ("HUAWEI".equalsIgnoreCase(Build.BRAND)) {
                fileName = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/";
            } else if ("HONOR".equalsIgnoreCase(Build.BRAND)) {
                fileName = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/";
            } else if ("OPPO".equalsIgnoreCase(Build.BRAND)) {
                fileName = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/";
            } else if ("vivo".equalsIgnoreCase(Build.BRAND)) {
                fileName = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/";
            } else if ("samsung".equalsIgnoreCase(Build.BRAND)) {
                fileName = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/";
            } else {
                fileName = Environment.getExternalStorageDirectory().getPath() + "/DCIM/";
            }
        }
        File file = new File(fileName);
        if (file.mkdirs()) {
            return fileName;
        }
        return fileName;
    }

2. 判斷Android Q 版本

/**
     * 判斷android Q  (10 ) 版本
     *
     * @return
     */
    public static boolean isAdndroidQ() {
        return Build.VERSION.SDK_INT >= 29;
    }

3. 復(fù)制文件

/**
     * 復(fù)制文件
     *
     * @param oldPathName
     * @param newPathName
     * @return
     */
    public static boolean copyFile(String oldPathName, String newPathName) {
        try {
            File oldFile = new File(oldPathName);
            if (!oldFile.exists()) {
                return false;
            } else if (!oldFile.isFile()) {
                return false;
            } else if (!oldFile.canRead()) {
                return false;
            }

            FileInputStream fileInputStream = new FileInputStream(oldPathName);
            FileOutputStream fileOutputStream = new FileOutputStream(newPathName);
            byte[] buffer = new byte[1024];
            int byteRead;
            while (-1 != (byteRead = fileInputStream.read(buffer))) {
                fileOutputStream.write(buffer, 0, byteRead);
            }
            fileInputStream.close();
            fileOutputStream.flush();
            fileOutputStream.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

4. 插入相冊


    /**
     * 插入相冊 部分機型適配(區(qū)分手機系統(tǒng)版本 Android Q)
     *
     * @param context
     * @param filePath
     * @return
     */
    public static boolean insertMediaPic(Context context, String filePath) {
        if (TextUtils.isEmpty(filePath)) return false;
        File file = new File(filePath);
        //判斷android Q  (10 ) 版本
        if (isAdndroidQ()) {
            if (file == null || !file.exists()) {
                return false;
            } else {
                try {
                    MediaStore.Images.Media.insertImage(context.getContentResolver(), file.getAbsolutePath(), file.getName(), null);
                    return true;
                } catch (Exception e) {
                    e.printStackTrace();
                    return false;
                }
            }
        } else {
            ContentValues values = new ContentValues();
            values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
            values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, System.currentTimeMillis() + "");
            context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + file.getAbsolutePath())));
            return true;
        }

    }

項目實現(xiàn)

1.Android Q 圖片存儲適配

1.1 res/xml/文件夾下 創(chuàng)建 app_files.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-path
            name="external_files"
            path="" />
        <path>
            <root-path
                name="root_path"
                path="." />
        </path>

        <external-path
            name="camera_photos"
            path="" />

        <external-path
            name="external_storage_root"
            path="." />
        <grant-uri-permission
            android:path="string"
            android:pathPattern="string"
            android:pathPrefix="string" />
    </paths>
</resources>

1.2 AndroidManifest.xml 中 app_files文件配置


AndroidManifest.xml.png

2.圖片 下載 以及保存(Kotlin 攜程下載圖片)

圖片存儲.png
package com.wu.material.activity

import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.databinding.DataBindingUtil
import com.bumptech.glide.Glide
import com.wu.material.R
import com.wu.material.databinding.ActivityCoroutinesBinding
import com.wu.material.util.FileSaveUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*


/**
 * @author wkq
 *
 * @date 2022年03月03日 12:44
 *
 *@des
 *
 */

class CoroutinesActivity : AppCompatActivity() {

    var databinding: ActivityCoroutinesBinding? = null
    //權(quán)限Code
    var REQUEST_CODE_LAUNCH = 10011
    var permissionsREAD = arrayOf(
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE)

    var path = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Ffile02.16sucai.com%2Fd%2Ffile%2F2014%2F0829%2F372edfeb74c3119b666237bd4af92be5.jpg&refer=http%3A%2F%2Ffile02.16sucai.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1648708406&t=ca9d3a371ddad53fbc5fa074db2090cc"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        databinding = DataBindingUtil.setContentView<ActivityCoroutinesBinding>(
            this,
            R.layout.activity_coroutines
        )
        // 判斷權(quán)限
       var isHave= checkPermissions(this,permissionsREAD,REQUEST_CODE_LAUNCH)
        if (isHave){
            showView()
        }
    }

    private fun showView() {
        Glide.with(this).load(path).into(databinding!!.ivIcon)
        databinding!!.btSave.setOnClickListener {
            savePic(path)
        }
    }




    fun savePic(path: String) {

        //攜程
        GlobalScope.launch(Dispatchers.IO) {
            var file = Glide.with(this@CoroutinesActivity).asFile().load(path).submit().get()
            Log.e("",file.absolutePath)
            // 文件夾位置
           var parentPath= FileSaveUtil.getPath(this@CoroutinesActivity)
            //文件名
           var fileName= System.currentTimeMillis().toString()+".png"
            //新文件文件地址
            var filePath=parentPath+fileName
            //復(fù)制地址(部分機型 不復(fù)制到指定文件夾,相冊不更新)
            FileSaveUtil.copyFile(file.path,filePath)

            var isSave=FileSaveUtil.insertMediaPic(this@CoroutinesActivity,filePath)

            withContext(Dispatchers.Main) {
                //主線程里更新 UI
                if (isSave){
                    Toast.makeText(this@CoroutinesActivity,"成功了",Toast.LENGTH_SHORT).show()
                }else{
                    Toast.makeText(this@CoroutinesActivity,"失敗了",Toast.LENGTH_SHORT).show()
                }
            }

        }
    }

    /**
     * 判斷權(quán)限
     */
    fun onRequestPermissionsResult(
        activity: Activity?,
        requestCode: Int,
        permissions: Array<String?>,
        grantResults: IntArray
    ): BooleanArray? {
        var result = true
        var isNerverAsk = false
        val length = grantResults.size
        for (i in 0 until length) {
            val permission = permissions[i]
            val grandResult = grantResults[i]
            if (grandResult == PackageManager.PERMISSION_DENIED) {
                result = false
                if (!ActivityCompat.shouldShowRequestPermissionRationale(
                        activity!!,
                        permission!!
                    )
                ) isNerverAsk = true
            }
        }
        return booleanArrayOf(result, isNerverAsk)
    }

    /**
     * 授權(quán)結(jié)果回調(diào)
     */
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_CODE_LAUNCH) {
            val hasPermissions = onRequestPermissionsResult(this, requestCode, permissions, grantResults)
            if (hasPermissions!![0]) {
                showView()
            } else {
                Toast.makeText(this@CoroutinesActivity,"沒權(quán)限",Toast.LENGTH_SHORT).show()

            }
        }
    }

    /**
     * 校驗權(quán)限
     */
    fun checkPermissions(
        activity: Activity?,
        permissions: Array<String>,
        requestCode: Int
    ): Boolean { //Android6.0以下默認(rèn)有權(quán)限
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true
        val needList: MutableList<String> = ArrayList()
        var needShowRationale = false
        val length = permissions.size
        for (i in 0 until length) {
            val permisson = permissions[i]
            if (TextUtils.isEmpty(permisson)) continue
            if (ActivityCompat.checkSelfPermission(activity!!, permisson)
                != PackageManager.PERMISSION_GRANTED
            ) {
                needList.add(permisson)
                if (ActivityCompat.shouldShowRequestPermissionRationale(
                        activity,
                        permisson
                    )
                ) needShowRationale = true
            }
        }
        return if (needList.size != 0) {
            if (needShowRationale) {
                //
                return false
            }
            ActivityCompat.requestPermissions(activity!!, needList.toTypedArray(), requestCode)
            false
        } else {
            true
        }
    }


}

注意:

  • 魅族手機個別版本下載到本地的圖片相冊刷新不出來
  • 個別手機相冊刷新會重復(fù)

總結(jié)

Android 系統(tǒng)隨著系統(tǒng)版本的更新,以及國內(nèi)各大廠商各大魔改 導(dǎo)致圖片下載相冊更新出現(xiàn)問題,這里項目中做的兼容做了記錄,隨后,項目中逐漸出現(xiàn)的問題再更新

寫作不易,歡迎點贊

參考文獻(xiàn)

1.Android Q 行為變更

2.Demo源碼地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末量九,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荠列,老刑警劉巖类浪,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異肌似,居然都是意外死亡费就,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門川队,熙熙樓的掌柜王于貴愁眉苦臉地迎上來力细,“玉大人,你說我怎么就攤上這事固额∶呗欤” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵斗躏,是天一觀的道長逝慧。 經(jīng)常有香客問我,道長啄糙,這世上最難降的妖魔是什么笛臣? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮隧饼,結(jié)果婚禮上沈堡,老公的妹妹穿的比我還像新娘。我一直安慰自己燕雁,他們只是感情好踱蛀,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贵白,像睡著了一般。 火紅的嫁衣襯著肌膚如雪崩泡。 梳的紋絲不亂的頭發(fā)上禁荒,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音角撞,去河邊找鬼呛伴。 笑死,一個胖子當(dāng)著我的面吹牛谒所,可吹牛的內(nèi)容都是我干的热康。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼劣领,長吁一口氣:“原來是場噩夢啊……” “哼姐军!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤奕锌,失蹤者是張志新(化名)和其女友劉穎著觉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惊暴,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡饼丘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辽话。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肄鸽。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖油啤,靈堂內(nèi)的尸體忽然破棺而出典徘,到底是詐尸還是另有隱情,我是刑警寧澤村砂,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布烂斋,位于F島的核電站,受9級特大地震影響础废,放射性物質(zhì)發(fā)生泄漏汛骂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一评腺、第九天 我趴在偏房一處隱蔽的房頂上張望帘瞭。 院中可真熱鬧,春花似錦蒿讥、人聲如沸蝶念。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽媒殉。三九已至,卻和暖如春摔敛,著一層夾襖步出監(jiān)牢的瞬間廷蓉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工马昙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留桃犬,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓行楞,卻偏偏與公主長得像攒暇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子子房,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容