Android问题篇之三方库引入(七)

511 阅读1分钟

三方库引入

4. Glide加载图片异常问题

  • com.bumptech.glide.load.resource.bitmap.VideoDecoder$VideoDecoderException: MediaMetadataRetriever failed to retrieve a frame without throwing, check the adb logs for .MetadataRetriever. prior to this exception for details

    • 加载非图片后缀链接(例非.jpg、.png等格式图片)报错
    • 加载涉及版权或加密图片(例https://cdn4.vectorstock.com地址图片)报错
  • 错误代码示例(不确定的链接最好不要用Glide直接加载)

val requestImageListener = object : RequestListener<Bitmap> {
    override fun onLoadFailed(
        e: GlideException?,
        model: Any?,
        target: Target<Bitmap>?,
        isFirstResource: Boolean
    ): Boolean {
        LogTool.d("loading failed")
        return true
    }

    override fun onResourceReady(
        resource: Bitmap?,
        model: Any?,
        target: Target<Bitmap>?,
        dataSource: DataSource?,
        isFirstResource: Boolean
    ): Boolean {
        if (resource == null) {
            LogTool.i("loading bitmap is null")
        } else {
           LogTool.i("loading bitmap success")
        }
        return true
    }
}

Glide.with(context)
    .asBitmap()
    .downsample(DownsampleStrategy.CENTER_INSIDE)
    .load(url)
    .timeout(15000)
    .listener(requestImageListener)
    .submit()
  • 正确示例(仅解决非法后缀问题)
// XxxApi.kt
@GET
suspend fun downloadImage(@Url url: String): Response<ResponseBody?>?

// @POST("xxx-api/xxx/downloadImages")
// suspend fun downloadImage(@Body url: Array<String>): ImageDownloadEntity?

// XxxRepository.kt
suspend fun downloadImage(url: String): Response<ResponseBody?>? = requestResponse { XxxClient.instance.downloadImage(url) }

suspend fun <T> requestResponse(requestCall: suspend () -> T?): T? =
    withContext(Dispatchers.IO) {
        withTimeout(15000) {
            requestCall()
        }
    }
    
// XxxClient.kt
private const val BASE_URL = "https://xxx.xxx.com/"

val instance: XxxApi by lazy {
    val okHttpClient = OkHttpClient.Builder()
        .connectTimeout(15, TimeUnit.SECONDS)
        .readTimeout(15, TimeUnit.SECONDS)
        .build()

    Retrofit.Builder()
        .baseUrl(BASE_URL)
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
        .create(XxxApi::class.java)
}

// XxxViewModel.kt
val imageFileData = MutableLiveData<File?>()
fun downloadImage(url: String) {
    launchUI(errorBlock = {
        imageFileData.value = null
    }, responseBlock = {
        val response = repository.downloadImage(url)
        if (response?.isSuccessful == true) {
            val fileName = "Xxx_${System.currentTimeMillis()}.jpg"
            LogTool.i("download fileName: $fileName")
            imageFileData.value = outputFile(fileName, response.body())
        } else {
            LogTool.w("download image error")
            imageFileData.value = null
        }
    })
}

fun launchUI(errorBlock: (e: Exception) -> Unit, responseBlock: suspend () -> Unit) {
    viewModelScope.launch(Dispatchers.Main) {
        safeApiCall(errorBlock = errorBlock, responseBlock)
    }
}

suspend fun <T> safeApiCall(
    errorBlock: suspend (e: Exception) -> Unit,
    responseBlock: suspend () -> T?
): T? {
    try {
        return responseBlock()
    } catch (e: Exception) {
        LogTool.e("Exception: ${e.message}")
        errorBlock(e)
    }
    return null
}

fun outputFile(name: String, body: ResponseBody?): File? {
    body?:return null
    val file = File(saveFolder(), name)
    if (file.exists()) {
        file.delete()
    }
    var inputStream: InputStream? = null
    var outputStream: OutputStream? = null
    val bytes = ByteArray(4096)
    val contentLength = body.contentLength()
    var downloadSize: Long = 0
    try {
        inputStream = body.byteStream()
        outputStream = FileOutputStream(file)
        var lastPre = -1
        while (true) {
            val read = inputStream.read(bytes)
            if (read == -1) {
                break
            }
            outputStream.write(bytes, 0, read)
            downloadSize += read
            val percent = ((downloadSize / contentLength).toDouble() * 100).roundToInt()
            if (lastPre != percent) {
                lastPre = percent
            }
        }
        outputStream.flush()
        return file
    } catch (e: IOException) {
        LogTool.e("e: $e")
    } finally {
        inputStream?.close()
        outputStream?.close()
    }
    return null
}

private fun saveFolder(): File {
    var file = File(
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
        "Xxx" + File.separator
    )
    if (!file.exists() && !file.mkdirs()) {
        file = Environment.getExternalStorageDirectory()
    }

    return file
}

3. 项目依赖多个三方库且三方库同时导入同名的.so文件,初始化时找不到so问题

  • 以如下两个依赖为例
    • 两个依赖同时引入了libc++_shared.so库,apk编译时最终只会保留一个同名的so库,无法保证保留的是哪个
implementation("org.opencv:opencv:4.11.0")
implementation("com.myscript:iink:3.0.0")
  • 解决思路
    • 1.若so位于某一个依赖库的lib中,可通过排除解决
    • 2.检查两依赖库版本,同时升至最新版本,保证依赖so版本一致
// 方法1(不一定有效,根据实际情况处理)
implementation('com.example:library:1.0.0') {
    exclude group: 'com.example', module: 'libexample'
}

// 方法2升至依赖最新版本(不一定最新,相匹配版本即可,亲测有效)
implementation("org.opencv:opencv:4.11.0")
implementation("com.myscript:iink:4.0.0")

2. 三方库引入权限查看

  • 生成apk时会同步生成manifest-merger-report.txt文件,可查看相关merge信息
  • build/outputs/logs/xxx-repoprt.txt

1. java.lang.UnsatisfiedLinkError: dlopen failed: "/data/app/~/com.xxx/lib/arm64/libc++_shared.so" has bad ELF magic: 00000000

接入MyScript OCR识别so库报错

  • build.gradle移除packagingOptions配置
android {
    packagingOptions {
        pickFirst 'lib/x86/libc++_shared.so'
        pickFirst 'lib/arm64-v8a/libc++_shared.so'
        pickFirst 'lib/x86_64/libc++_shared.so'
        pickFirst 'lib/armeabi-v7a/libc++_shared.so'
    }
    ...
}
  • 添加MyScript混淆(proguard-rules.pro)
-keep class com.myscript.** {*;}