把网络中的Gif和图片下载到本地,并通知系统相册显示,同时适配了Android Q版本,上代码(kotlin):
thread { //kotlin的异步代码块,并不是缩写,而是直接能运行
var path = DownloadUtils.showLoadingImage(this, mUrl) //必须异步调用处
if (!path.isNullOrEmpty()) {
runOnUiThread {
ToastUtil.showLong("保存成功")
}
}
}
showLoadingImage() 方法
/**
* 下载图片保存至手机并通知系统相册显示
*/
fun showLoadingImage(context: Context, urlPath: String): String? {
if (urlPath.isNullOrEmpty()) return null
var mMimeType = urlPath.substring(urlPath.lastIndexOf("."), urlPath.length) // .gif还是.jpg
var outImageUri: Uri? = null
var outputStream: OutputStream? = null
var inputStream: InputStream? = null
var inBuffer: BufferedSource? = null
try {
if (SdkVersionUtils.checkedAndroid_Q()) { //针对Q版本创建uri
val contentValues = ContentValues()
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, "IMG_" + SimpleDateFormat("yyyyMMdd_HHmmssSS").format(System.currentTimeMillis()) + mMimeType)
contentValues.put(MediaStore.Images.Media.DATE_TAKEN, ValueOf.toString(System.currentTimeMillis()))
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/*")
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, PictureMimeType.DCIM)
outImageUri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
} else {
val state = Environment.getExternalStorageState()
val rootDir = if (state == Environment.MEDIA_MOUNTED) Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) else context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
if (rootDir != null) {
if (!rootDir.exists()) {
rootDir.mkdirs()
}
val folderDir = File(if (state != Environment.MEDIA_MOUNTED) rootDir.absolutePath else rootDir.absolutePath + File.separator + PictureMimeType.CAMERA + File.separator)
if (!folderDir.exists() && folderDir.mkdirs()) {
}
val fileName ="IMG_" + SimpleDateFormat("yyyyMMdd_HHmmssSS").format(System.currentTimeMillis()) + mMimeType
val file = File(folderDir, fileName)
outImageUri = Uri.fromFile(file)
}
}
if (outImageUri != null) {
outputStream = Objects.requireNonNull(context.getContentResolver().openOutputStream(outImageUri))
val u = URL(urlPath)
inputStream = u.openStream()
inBuffer = inputStream.source().buffer()
val bufferCopy = MyFileUtils.bufferCopy(inBuffer, outputStream)
if (bufferCopy) {
var path = MyFileUtils.getPath(context, outImageUri)
/*--------------通知相册广播-----------------------------*/
// deprecated (4.4以后只有系统级应用才有使用广播通知系统扫描的权限)
val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
intent.data = outImageUri
context.sendBroadcast(intent)
MediaScannerConnection.scanFile(context, arrayOf(path), null) { path, uri -> Log.e("scan file ", "$path---$uri") }
/*--------------通知广播-----------------------------*/
return path
}
}
} catch (e: Exception) {
if (outImageUri != null && SdkVersionUtils.checkedAndroid_Q()) {
context.getContentResolver().delete(outImageUri, null, null)
}
} finally {
inputStream?.close()
outputStream?.close()
inBuffer?.close()
}
return null
}
MyFileUtils类中的方法直接全copy,里面也包含bufferCopy()方法
import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.util.Log
import okio.BufferedSink
import okio.BufferedSource
import okio.buffer
import okio.sink
import java.io.OutputStream
import java.util.*
class MyFileUtils{
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
* @author paulburke
*/
fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
* @author paulburke
*/
fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.<br></br>
* <br></br>
* Callers should check whether the path is local before assuming it
* represents a local file.
*
* @param context The context.
* @param uri The Uri to query.
* @author paulburke
*/
@SuppressLint("NewApi")
fun getPath(ctx: Context, uri: Uri): String? {
val context = ctx.applicationContext
val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
context.getExternalFilesDir(Environment.DIRECTORY_PICTURES).toString() + "/" + split[1]
} else {
Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
}
} else if (isDownloadsDocument(uri)) {
val id = DocumentsContract.getDocumentId(uri)
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
return getDataColumn(context, contentUri, null, null)
} else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":").toTypedArray()
val type = split[0]
var contentUri: Uri? = null
if ("image" == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if ("video" == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
return getDataColumn(context, contentUri, selection, selectionArgs)
}
} else if ("content".equals(uri.scheme, ignoreCase = true)) {
// Return the remote address
return if (isGooglePhotosUri(uri)) {
uri.lastPathSegment
} else getDataColumn(context, uri, null, null)
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path
}
return null
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
* @author paulburke
*/
fun getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array<String>?): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(column)
try {
cursor = context.contentResolver.query(uri!!, projection, selection, selectionArgs, null)
if (cursor != null && cursor.moveToFirst()) {
val column_index = cursor.getColumnIndexOrThrow(column)
return cursor.getString(column_index)
}
} catch (ex: IllegalArgumentException) {
Log.i("Query", String.format(Locale.getDefault(), "getDataColumn: _data - [%s]", ex.message))
} finally {
cursor?.close()
}
return null
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
* @author paulburke
*/
fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
fun isGooglePhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.content" == uri.authority
}
fun bufferCopy(inBuffer: BufferedSource?, outputStream: OutputStream?): Boolean {
var outBuffer: BufferedSink? = null
try {
outBuffer = outputStream?.sink()?.buffer()
inBuffer?.let {
outBuffer?.writeAll(it)
}
outBuffer?.flush()
return true
} catch (e: Exception) {
e.printStackTrace()
} finally {
inBuffer?.close()
outBuffer?.close()
}
return false
}
}
网友评论