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)的問題再更新