parent
dc58940561
commit
df1349c089
@ -1,112 +1,75 @@ |
||||
package com.gyf.csams |
||||
|
||||
import android.app.Application |
||||
import android.content.res.Resources |
||||
import android.graphics.Bitmap |
||||
import android.graphics.BitmapFactory |
||||
import android.util.LruCache |
||||
import androidx.compose.ui.graphics.ImageBitmap |
||||
import androidx.compose.ui.graphics.asImageBitmap |
||||
import coil.ImageLoader |
||||
import coil.ImageLoaderFactory |
||||
import coil.imageLoader |
||||
import coil.memory.MemoryCache |
||||
import coil.request.ImageRequest |
||||
import coil.util.CoilUtils |
||||
import com.gyf.csams.uikit.BackgroundImage |
||||
import com.gyf.csams.util.ImageUtil |
||||
import com.orhanobut.logger.AndroidLogAdapter |
||||
import com.orhanobut.logger.DiskLogAdapter |
||||
import com.orhanobut.logger.Logger |
||||
import okhttp3.OkHttpClient |
||||
|
||||
class APP : Application() { |
||||
class APP : Application(), ImageLoaderFactory { |
||||
|
||||
private val backgroundImage = mutableMapOf<BackgroundImage, MemoryCache.Key?>() |
||||
|
||||
private lateinit var memoryCache: LruCache<BackgroundImage, Bitmap> |
||||
|
||||
// Get max available VM memory, exceeding this amount will throw an |
||||
// OutOfMemory exception. Stored in kilobytes as LruCache takes an |
||||
// int in its constructor. |
||||
private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt() |
||||
|
||||
// Use 1/8th of the available memory for this memory cache. |
||||
val cacheSize = maxMemory / 8 |
||||
|
||||
fun getImage(image: BackgroundImage, reqWidth: Int, reqHeight: Int): ImageBitmap { |
||||
val bitmap = memoryCache.get(image) |
||||
return if (bitmap == null) { |
||||
Logger.i("reqWidth=$reqWidth,reqHeight=$reqHeight") |
||||
val cacheValue = decodeSampledBitmapFromResource( |
||||
res = resources, |
||||
image.id, |
||||
reqWidth = reqWidth, |
||||
reqHeight = reqHeight |
||||
) |
||||
memoryCache.put(image, cacheValue) |
||||
Logger.i("添加缓存:${image}") |
||||
cacheValue.asImageBitmap() |
||||
suspend fun getImage(image: BackgroundImage): ImageBitmap? { |
||||
val key = backgroundImage[image] |
||||
if (key != null) { |
||||
return applicationContext.imageLoader.memoryCache[key]?.asImageBitmap() |
||||
?: ImageUtil.getImage(applicationContext, image.id) |
||||
} else { |
||||
Logger.i("从缓存读取:${image}") |
||||
bitmap.asImageBitmap() |
||||
throw IllegalArgumentException("无法从${key}获取背景图!") |
||||
} |
||||
} |
||||
|
||||
private fun decodeSampledBitmapFromResource( |
||||
res: Resources, |
||||
resId: Int, |
||||
reqWidth: Int, |
||||
reqHeight: Int |
||||
): Bitmap { |
||||
// First decode with inJustDecodeBounds=true to check dimensions |
||||
return BitmapFactory.Options().run { |
||||
inJustDecodeBounds = true |
||||
BitmapFactory.decodeResource(res, resId, this) |
||||
|
||||
// Calculate inSampleSize |
||||
inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight) |
||||
|
||||
// Decode bitmap with inSampleSize set |
||||
inJustDecodeBounds = false |
||||
|
||||
BitmapFactory.decodeResource(res, resId, this) |
||||
override fun newImageLoader(): ImageLoader { |
||||
return ImageLoader.Builder(applicationContext) |
||||
.crossfade(true) |
||||
.okHttpClient { |
||||
OkHttpClient.Builder() |
||||
.cache(CoilUtils.createDefaultCache(applicationContext)) |
||||
.build() |
||||
} |
||||
.build() |
||||
} |
||||
|
||||
private fun calculateInSampleSize( |
||||
options: BitmapFactory.Options, |
||||
reqWidth: Int, |
||||
reqHeight: Int |
||||
): Int { |
||||
// Raw height and width of image |
||||
val (height: Int, width: Int) = options.run { outHeight to outWidth } |
||||
var inSampleSize = 1 |
||||
|
||||
if (height > reqHeight || width > reqWidth) { |
||||
|
||||
val halfHeight: Int = height / 2 |
||||
val halfWidth: Int = width / 2 |
||||
|
||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both |
||||
// height and width larger than the requested height and width. |
||||
while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) { |
||||
inSampleSize *= 2 |
||||
/** |
||||
* 预加载背景图 |
||||
* |
||||
*/ |
||||
private fun preloadImage() { |
||||
BackgroundImage.values().forEach { |
||||
Logger.i("预加载背景图:${it.name}") |
||||
val request = ImageRequest.Builder(applicationContext) |
||||
.data(it.id) |
||||
// Optional, but setting a ViewSizeResolver will conserve memory by limiting the size the image should be preloaded into memory at. |
||||
.size(800, 600) |
||||
.listener { _, metadata -> |
||||
Logger.i("metadata.memoryCacheKey=${metadata.memoryCacheKey}") |
||||
backgroundImage[it] = metadata.memoryCacheKey |
||||
} |
||||
.build() |
||||
applicationContext.imageLoader.enqueue(request = request) |
||||
} |
||||
|
||||
return inSampleSize |
||||
} |
||||
|
||||
override fun onCreate() { |
||||
super.onCreate() |
||||
|
||||
memoryCache = object : LruCache<BackgroundImage, Bitmap>(cacheSize) { |
||||
|
||||
override fun sizeOf(key: BackgroundImage, bitmap: Bitmap): Int { |
||||
// The cache size will be measured in kilobytes rather than |
||||
// number of items. |
||||
return bitmap.byteCount / 1024 |
||||
} |
||||
} |
||||
|
||||
//初始化日志 |
||||
Logger.addLogAdapter(AndroidLogAdapter()) |
||||
Logger.addLogAdapter(DiskLogAdapter()) |
||||
|
||||
Logger.i("${BuildConfig.foreground_app_name}启动") |
||||
|
||||
|
||||
preloadImage() |
||||
} |
||||
} |
@ -0,0 +1,46 @@ |
||||
package com.gyf.csams.util |
||||
|
||||
import android.content.Context |
||||
import androidx.compose.ui.graphics.ImageBitmap |
||||
import androidx.compose.ui.graphics.asImageBitmap |
||||
import androidx.core.graphics.drawable.toBitmap |
||||
import coil.imageLoader |
||||
import coil.request.ErrorResult |
||||
import coil.request.ImageRequest |
||||
import coil.request.SuccessResult |
||||
import com.orhanobut.logger.Logger |
||||
|
||||
class ImageUtil { |
||||
companion object { |
||||
/** |
||||
* |
||||
* |
||||
* @param data Set the data to load. |
||||
* The default supported data types are: |
||||
* String (mapped to a Uri) |
||||
* Uri ("android.resource", "content", "file", "http", and "https" schemes only) |
||||
* HttpUrl |
||||
* File |
||||
* DrawableRes |
||||
* Drawable |
||||
* Bitmap |
||||
*/ |
||||
suspend fun getImage(context: Context, data: Any?): ImageBitmap? { |
||||
val request = ImageRequest.Builder(context) |
||||
.data(data = data) |
||||
.build() |
||||
return when (val result = context.imageLoader.execute(request)) { |
||||
is SuccessResult -> result.drawable.toBitmap().asImageBitmap() |
||||
is ErrorResult -> { |
||||
val drawable = result.drawable |
||||
return if (drawable == null) { |
||||
Logger.e("${data}图片加载失败") |
||||
null |
||||
} else { |
||||
drawable.toBitmap().asImageBitmap() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,6 +1,5 @@ |
||||
#Sat Apr 17 15:25:36 CST 2021 |
||||
distributionBase=GRADLE_USER_HOME |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip |
||||
distributionPath=wrapper/dists |
||||
zipStorePath=wrapper/dists |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip |
||||
zipStoreBase=GRADLE_USER_HOME |
||||
zipStorePath=wrapper/dists |
||||
|
@ -0,0 +1 @@ |
||||
/build |
@ -0,0 +1,106 @@ |
||||
plugins { |
||||
id("com.android.library") |
||||
id("kotlin-android") |
||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.4.32" |
||||
} |
||||
|
||||
android { |
||||
compileSdk = 30 |
||||
|
||||
defaultConfig { |
||||
minSdk = 21 |
||||
targetSdk = 30 |
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" |
||||
consumerProguardFiles("consumer-rules.pro") |
||||
} |
||||
|
||||
buildTypes { |
||||
release { |
||||
isMinifyEnabled = false |
||||
proguardFiles( |
||||
getDefaultProguardFile("proguard-android-optimize.txt"), |
||||
"proguard-rules.pro" |
||||
) |
||||
} |
||||
} |
||||
compileOptions { |
||||
sourceCompatibility = JavaVersion.VERSION_1_8 |
||||
targetCompatibility = JavaVersion.VERSION_1_8 |
||||
} |
||||
kotlinOptions { |
||||
jvmTarget = "1.8" |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
/** |
||||
* 针对最新的平台功能和 API 调整应用,同时还支持旧设备。 |
||||
* https://developer.android.com/jetpack/androidx/releases/core |
||||
*/ |
||||
api("androidx.core:core-ktx:1.3.2") |
||||
/** |
||||
* 允许在平台旧版 API 上访问新 API(很多使用 Material Design)。 |
||||
* https://developer.android.com/jetpack/androidx/releases/appcompat |
||||
*/ |
||||
api("androidx.appcompat:appcompat:1.2.0") |
||||
/** |
||||
* 与设备互动所需的 Compose UI 的基本组件,包括布局、绘图和输入。 |
||||
* https://developer.android.com/jetpack/androidx/releases/compose-ui |
||||
*/ |
||||
api("androidx.compose.ui:ui:${rootProject.extra["compose_version"]}") |
||||
api("androidx.compose.ui:ui-tooling:${rootProject.extra["compose_version"]}") |
||||
/** |
||||
* Compose 的编程模型和状态管理的基本构建块,以及 Compose 编译器插件针对的核心运行时。 |
||||
* https://developer.android.com/jetpack/androidx/releases/compose-runtime |
||||
*/ |
||||
api("androidx.compose.runtime:runtime-livedata:${rootProject.extra["compose_version"]}") |
||||
//Material Components |
||||
api("com.google.android.material:material:1.3.0") |
||||
/** |
||||
* 使用现成可用的 Material Design 组件构建 Jetpack Compose UI。这是更高层级的 Compose 入口点,旨在提供与 www.material.io 上描述的组件一致的组件。 |
||||
* https://developer.android.com/jetpack/androidx/releases/compose-material |
||||
*/ |
||||
api("androidx.compose.material:material:${rootProject.extra["compose_version"]}") |
||||
/** |
||||
* 生命周期感知型组件 |
||||
* https://developer.android.com/jetpack/androidx/releases/lifecycle |
||||
*/ |
||||
api("androidx.lifecycle:lifecycle-runtime-ktx:${rootProject.extra["lifecycle_version"]}") |
||||
api("androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha04") |
||||
/** |
||||
* 访问基于 Activity 构建的可组合 API。 |
||||
* https://developer.android.com/jetpack/androidx/releases/activity |
||||
*/ |
||||
api("androidx.activity:activity-compose:1.3.0-alpha07") |
||||
/** |
||||
* Simple, pretty and powerful logger for android |
||||
* https://github.com/orhanobut/logger |
||||
*/ |
||||
api("com.orhanobut:logger:2.2.0") |
||||
/** |
||||
* |
||||
* https://github.com/square/okhttp |
||||
*/ |
||||
api("com.squareup.okhttp3:okhttp:4.9.1") |
||||
/** |
||||
* https://github.com/google/gson |
||||
*/ |
||||
api("com.google.code.gson:gson:2.8.6") |
||||
/** |
||||
* https://kotlinlang.org/docs/serialization.html |
||||
*/ |
||||
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0") |
||||
/** |
||||
* https://developer.android.com/jetpack/androidx/releases/navigation |
||||
*/ |
||||
api("androidx.navigation:navigation-compose:1.0.0-alpha10") |
||||
/** |
||||
* https://developer.android.com/jetpack/androidx/releases/room |
||||
*/ |
||||
api("androidx.room:room-runtime:${rootProject.extra["room_version"]}") |
||||
// optional - Kotlin Extensions and Coroutines support for Room |
||||
api("androidx.room:room-ktx:${rootProject.extra["room_version"]}") |
||||
// https://github.com/coil-kt/coil/blob/master/README-zh.md |
||||
api("io.coil-kt:coil:1.2.1") |
||||
} |
@ -0,0 +1,21 @@ |
||||
# Add project specific ProGuard rules here. |
||||
# You can control the set of applied configuration files using the |
||||
# proguardFiles setting in build.gradle. |
||||
# |
||||
# For more details, see |
||||
# http://developer.android.com/guide/developing/tools/proguard.html |
||||
|
||||
# If your project uses WebView with JS, uncomment the following |
||||
# and specify the fully qualified class name to the JavaScript interface |
||||
# class: |
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||
# public *; |
||||
#} |
||||
|
||||
# Uncomment this to preserve the line number information for |
||||
# debugging stack traces. |
||||
#-keepattributes SourceFile,LineNumberTable |
||||
|
||||
# If you keep the line number information, uncomment this to |
||||
# hide the original source file name. |
||||
#-renamesourcefileattribute SourceFile |
@ -0,0 +1,22 @@ |
||||
package com.gyf.lib |
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||
import androidx.test.platform.app.InstrumentationRegistry |
||||
import org.junit.Assert.* |
||||
import org.junit.Test |
||||
import org.junit.runner.RunWith |
||||
|
||||
/** |
||||
* Instrumented test, which will execute on an Android device. |
||||
* |
||||
* See [testing documentation](http://d.android.com/tools/testing). |
||||
*/ |
||||
@RunWith(AndroidJUnit4::class) |
||||
class ExampleInstrumentedTest { |
||||
@Test |
||||
fun useAppContext() { |
||||
// Context of the app under test. |
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext |
||||
assertEquals("com.gyf.lib.test", appContext.packageName) |
||||
} |
||||
} |
@ -0,0 +1,4 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest package="com.gyf.lib"> |
||||
|
||||
</manifest> |
@ -0,0 +1,16 @@ |
||||
package com.gyf.lib |
||||
|
||||
import org.junit.Assert.* |
||||
import org.junit.Test |
||||
|
||||
/** |
||||
* Example local unit test, which will execute on the development machine (host). |
||||
* |
||||
* See [testing documentation](http://d.android.com/tools/testing). |
||||
*/ |
||||
class ExampleUnitTest { |
||||
@Test |
||||
fun addition_isCorrect() { |
||||
assertEquals(4, 2 + 2) |
||||
} |
||||
} |
Loading…
Reference in new issue