Android 常見(jiàn)功能保存圖片十分常用,近年來(lái)隨著Android版本更新,逐漸收緊了App的權(quán)限,導(dǎo)致App存儲(chǔ)圖片需要做的兼容性問(wèn)題越來(lái)越多.
原因:
- 廠(chǎng)商定制存儲(chǔ)方式
- 不同版本存儲(chǔ)方式不一致
- Android Q 沙盒機(jī)制
導(dǎo)致的問(wèn)題:
- 文件存儲(chǔ)異常
- 相冊(cè)不展示下載的圖片
- 相冊(cè)展示重復(fù)的下載圖片
Android Q (10) 新增了分區(qū)存儲(chǔ)
針對(duì)外部存儲(chǔ)的過(guò)濾視圖,可提供對(duì)特定于應(yīng)用的文件和媒體集合的訪(fǎng)問(wèn)權(quán)限,所以圖片保存的時(shí)候需要存儲(chǔ)到指定App文件夾下才能保存文件
兼容實(shí)現(xiàn):
1. 處理Android Q 存儲(chǔ)地址問(wèn)題
/**
* 根據(jù) Android Q 區(qū)分地址
*
* @param context
* @return
*/
public static String getPath(Context context) {
// equalsIgnoreCase() 忽略大小寫(xiě)
String fileName = "";
if (Build.VERSION.SDK_INT >= 29) {
fileName = context.getExternalFilesDir("").getAbsolutePath() + "/current/";
} else {
if ("Xiaomi".equalsIgnoreCase(Build.BRAND)) { // 小米手機(jī)
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. 插入相冊(cè)
/**
* 插入相冊(cè) 部分機(jī)型適配(區(qū)分手機(jī)系統(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àng)目實(shí)現(xiàn)
1.Android Q 圖片存儲(chǔ)適配
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 攜程下載圖片)

圖片存儲(chǔ).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ù)制地址(部分機(jī)型 不復(fù)制到指定文件夾,相冊(cè)不更新)
FileSaveUtil.copyFile(file.path,filePath)
var isSave=FileSaveUtil.insertMediaPic(this@CoroutinesActivity,filePath)
withContext(Dispatchers.Main) {
//主線(xiàn)程里更新 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,"沒(méi)權(quán)限",Toast.LENGTH_SHORT).show()
}
}
}
/**
* 校驗(yàn)權(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
}
}
}
注意:
- 魅族手機(jī)個(gè)別版本下載到本地的圖片相冊(cè)刷新不出來(lái)
- 個(gè)別手機(jī)相冊(cè)刷新會(huì)重復(fù)
總結(jié)
Android 系統(tǒng)隨著系統(tǒng)版本的更新,以及國(guó)內(nèi)各大廠(chǎng)商各大魔改 導(dǎo)致圖片下載相冊(cè)更新出現(xiàn)問(wèn)題,這里項(xiàng)目中做的兼容做了記錄,隨后,項(xiàng)目中逐漸出現(xiàn)的問(wèn)題再更新