主页固定显示

This commit is contained in:
2025-10-20 21:30:28 +08:00
parent b1f21fd4f7
commit 68b2e0754c
74 changed files with 4881 additions and 34 deletions

1
iconloader/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

37
iconloader/build.gradle Normal file
View File

@@ -0,0 +1,37 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 36
// buildToolsVersion "36.0.0"
defaultConfig {
minSdkVersion 14
targetSdkVersion 36
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.3.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
//磁盘缓存
implementation 'com.jakewharton:disklrucache:2.0.2'
}

View File

21
iconloader/proguard-rules.pro vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,27 @@
package com.uiui.iconloader;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.ttstd.iconloader.test", appContext.getPackageName());
}
}

View File

@@ -0,0 +1 @@
<manifest package="com.uiui.iconloader" />

View File

@@ -0,0 +1,16 @@
package com.uiui.iconloader;
import android.content.ComponentName;
import android.content.Intent;
public class CacheKeyGenerator {
// 生成ComponentName的标准字符串格式包名/类名)
public static String generateKey(ComponentName componentName) {
return componentName.flattenToShortString(); // 如com.example.app/.MainActivity
}
// 从Intent中提取ComponentName作为键
public static String generateKey(Intent intent) {
return generateKey(intent.getComponent());
}
}

View File

@@ -0,0 +1,79 @@
package com.uiui.iconloader;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import androidx.collection.LruCache;
import com.jakewharton.disklrucache.DiskLruCache;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class IconCacheManager {
private static final String TAG = "IconCacheManager";
private LruCache<String, Drawable> mMemoryCache;
private DiskLruCache mDiskCache;
public IconCacheManager(Context context) {
// 内存缓存最大8MB
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Drawable>(cacheSize) {
@Override
protected int sizeOf(String key, Drawable icon) {
if (icon instanceof BitmapDrawable) {
return ((BitmapDrawable) icon).getBitmap().getByteCount() / 1024;
}
return 1; // 非Bitmap对象按1KB计算
}
};
// 磁盘缓存50MB
File cacheDir = new File(context.getCacheDir(), "icon_cache");
try {
mDiskCache = DiskLruCache.open(cacheDir, 1, 1, 50 * 1024 * 1024);
} catch (Exception e) {
Log.e(TAG, "IconCacheManager: " + e.getMessage());
}
}
public Drawable getFromMemory(String key) {
return mMemoryCache.get(key);
}
public Drawable getFromDisk(String key) {
try {
DiskLruCache.Snapshot snapshot = mDiskCache.get(key);
if (snapshot != null) {
InputStream in = snapshot.getInputStream(0);
return Drawable.createFromStream(in, null);
}
} catch (IOException e) {
Log.e("IconCache", "Disk read error", e);
}
return null;
}
public void putToMemory(String key, Drawable icon) {
mMemoryCache.put(key, icon);
}
public void putToDisk(String key, Drawable icon) {
try {
DiskLruCache.Editor editor = mDiskCache.edit(key);
if (icon instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) icon).getBitmap();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, editor.newOutputStream(0));
editor.commit();
}
} catch (IOException e) {
Log.e("IconCache", "Disk write error", e);
}
}
}

View File

@@ -0,0 +1,97 @@
package com.uiui.iconloader;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import androidx.loader.content.AsyncTaskLoader;
public class IconLoader extends AsyncTaskLoader<Drawable> {
private final ComponentName mComponentName; // 关键使用ComponentName标识组件
private final IconCacheManager mCache;
public IconLoader(Context context, ComponentName componentName, IconCacheManager cache) {
super(context);
mComponentName = componentName;
mCache = cache;
}
@Override
protected void onStartLoading() {
String key = CacheKeyGenerator.generateKey(mComponentName);
// 1. 检查内存缓存
Drawable cachedIcon = mCache.getFromMemory(key);
if (cachedIcon != null) {
deliverResult(cachedIcon); // 直接返回缓存
return;
}
forceLoad(); // 触发后台加载
}
@Override
public Drawable loadInBackground() {
String key = CacheKeyGenerator.generateKey(mComponentName);
// 1. 检查内存缓存
Drawable cachedIcon = mCache.getFromMemory(key);
if (cachedIcon != null) return cachedIcon;
// 2. 检查磁盘缓存
Drawable diskCached = mCache.getFromDisk(key);
if (diskCached != null) {
mCache.putToMemory(key, diskCached);
return diskCached;
}
// 3. 从PackageManager加载组件专属图标
try {
PackageManager pm = getContext().getPackageManager();
ActivityInfo info = pm.getActivityInfo(mComponentName, 0); // 获取ActivityInfo
Drawable rawIcon = info.loadIcon(pm); // 加载该组件的图标
// 4. 图标处理(压缩/主题适配)
Drawable processedIcon = processIcon(rawIcon);
// 5. 更新缓存
mCache.putToMemory(key, processedIcon);
mCache.putToDisk(key, processedIcon);
return processedIcon;
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}
private Drawable processIcon(Drawable icon) {
// 自适应图标处理Android 8.0+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
&& icon instanceof AdaptiveIconDrawable) {
AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) icon;
int targetColor = isDarkMode() ? Color.WHITE : Color.BLACK;
adaptiveIcon.setColorFilter(targetColor, PorterDuff.Mode.SRC_ATOP); // 根据主题切换单色模式
}
// 图标尺寸压缩(避免内存溢出)
if (icon instanceof BitmapDrawable) {
Bitmap bitmap = ((BitmapDrawable) icon).getBitmap();
int scaledWidth = bitmap.getWidth() / 2;
int scaledHeight = bitmap.getHeight() / 2;
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true);
return new BitmapDrawable(getContext().getResources(), scaledBitmap);
}
return icon;
}
private boolean isDarkMode() {
return (getContext().getResources().getConfiguration().uiMode
& Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
}
}

View File

@@ -0,0 +1,17 @@
package com.uiui.iconloader;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}