Android 常见功能保存图片十分常用,近年来随着Android版本更新,逐渐收紧了App的权限,导致App存储图片需要做的兼容性问题越来越多.
原因:
- 厂商定制存储方式
- 不同版本存储方式不一致
- Android Q 沙盒机制
导致的问题:
- 文件存储异常
- 相册不展示下载的图片
- 相册展示重复的下载图片
Android Q (10) 新增了分区存储
针对外部存储的过滤视图,可提供对特定于应用的文件和媒体集合的访问权限,所以图片保存的时候需要存储到指定App文件夹下才能保存文件
兼容实现:
1. 处理Android Q 存储地址问题
/**
* 根据 Android 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. 复制文件
/**
* 复制文件
*
* @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. 插入相册
/**
* 插入相册 部分机型适配(区分手机系统版本 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;
}
}
项目实现
1.Android Q 图片存储适配
1.1 res/xml/文件夹下 创建 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文件配置
![](https://img.haomeiwen.com/i5317456/bcbf00c1783839d7.png)
2.图片 下载 以及保存(Kotlin 携程下载图片)
![](https://img.haomeiwen.com/i5317456/ca46c6cef9565a7c.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
//权限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
)
// 判断权限
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
//复制地址(部分机型 不复制到指定文件夹,相册不更新)
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()
}
}
}
}
/**
* 判断权限
*/
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)
}
/**
* 授权结果回调
*/
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,"没权限",Toast.LENGTH_SHORT).show()
}
}
}
/**
* 校验权限
*/
fun checkPermissions(
activity: Activity?,
permissions: Array<String>,
requestCode: Int
): Boolean { //Android6.0以下默认有权限
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
}
}
}
注意:
- 魅族手机个别版本下载到本地的图片相册刷新不出来
- 个别手机相册刷新会重复
总结
Android 系统随着系统版本的更新,以及国内各大厂商各大魔改 导致图片下载相册更新出现问题,这里项目中做的兼容做了记录,随后,项目中逐渐出现的问题再更新
网友评论