parent
dc58940561
commit
df1349c089
@ -1,112 +1,75 @@ |
|||||||
package com.gyf.csams |
package com.gyf.csams |
||||||
|
|
||||||
import android.app.Application |
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.ImageBitmap |
||||||
import androidx.compose.ui.graphics.asImageBitmap |
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.uikit.BackgroundImage |
||||||
|
import com.gyf.csams.util.ImageUtil |
||||||
import com.orhanobut.logger.AndroidLogAdapter |
import com.orhanobut.logger.AndroidLogAdapter |
||||||
import com.orhanobut.logger.DiskLogAdapter |
import com.orhanobut.logger.DiskLogAdapter |
||||||
import com.orhanobut.logger.Logger |
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> |
suspend fun getImage(image: BackgroundImage): ImageBitmap? { |
||||||
|
val key = backgroundImage[image] |
||||||
// Get max available VM memory, exceeding this amount will throw an |
if (key != null) { |
||||||
// OutOfMemory exception. Stored in kilobytes as LruCache takes an |
return applicationContext.imageLoader.memoryCache[key]?.asImageBitmap() |
||||||
// int in its constructor. |
?: ImageUtil.getImage(applicationContext, image.id) |
||||||
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() |
|
||||||
} else { |
} else { |
||||||
Logger.i("从缓存读取:${image}") |
throw IllegalArgumentException("无法从${key}获取背景图!") |
||||||
bitmap.asImageBitmap() |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
private fun decodeSampledBitmapFromResource( |
override fun newImageLoader(): ImageLoader { |
||||||
res: Resources, |
return ImageLoader.Builder(applicationContext) |
||||||
resId: Int, |
.crossfade(true) |
||||||
reqWidth: Int, |
.okHttpClient { |
||||||
reqHeight: Int |
OkHttpClient.Builder() |
||||||
): Bitmap { |
.cache(CoilUtils.createDefaultCache(applicationContext)) |
||||||
// First decode with inJustDecodeBounds=true to check dimensions |
.build() |
||||||
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) |
|
||||||
} |
} |
||||||
|
.build() |
||||||
} |
} |
||||||
|
|
||||||
private fun calculateInSampleSize( |
/** |
||||||
options: BitmapFactory.Options, |
* 预加载背景图 |
||||||
reqWidth: Int, |
* |
||||||
reqHeight: Int |
*/ |
||||||
): Int { |
private fun preloadImage() { |
||||||
// Raw height and width of image |
BackgroundImage.values().forEach { |
||||||
val (height: Int, width: Int) = options.run { outHeight to outWidth } |
Logger.i("预加载背景图:${it.name}") |
||||||
var inSampleSize = 1 |
val request = ImageRequest.Builder(applicationContext) |
||||||
|
.data(it.id) |
||||||
if (height > reqHeight || width > reqWidth) { |
// Optional, but setting a ViewSizeResolver will conserve memory by limiting the size the image should be preloaded into memory at. |
||||||
|
.size(800, 600) |
||||||
val halfHeight: Int = height / 2 |
.listener { _, metadata -> |
||||||
val halfWidth: Int = width / 2 |
Logger.i("metadata.memoryCacheKey=${metadata.memoryCacheKey}") |
||||||
|
backgroundImage[it] = metadata.memoryCacheKey |
||||||
// 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 |
|
||||||
} |
} |
||||||
|
.build() |
||||||
|
applicationContext.imageLoader.enqueue(request = request) |
||||||
} |
} |
||||||
|
|
||||||
return inSampleSize |
|
||||||
} |
} |
||||||
|
|
||||||
override fun onCreate() { |
override fun onCreate() { |
||||||
super.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(AndroidLogAdapter()) |
||||||
Logger.addLogAdapter(DiskLogAdapter()) |
Logger.addLogAdapter(DiskLogAdapter()) |
||||||
|
|
||||||
Logger.i("${BuildConfig.foreground_app_name}启动") |
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 |
distributionBase=GRADLE_USER_HOME |
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip |
|
||||||
distributionPath=wrapper/dists |
distributionPath=wrapper/dists |
||||||
zipStorePath=wrapper/dists |
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip |
||||||
zipStoreBase=GRADLE_USER_HOME |
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