diff --git a/README.md b/README.md index 1ef0db4..164fb16 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![](https://img.shields.io/badge/Android-4.4%20--%2013-blue.svg?style=flat) ![](https://img.shields.io/badge/arch-armeabi--v7a%20%7C%20arm64--v8a-blue.svg?style=flat) -![](https://img.shields.io/badge/release-1.0.8-red.svg?style=flat) +![](https://img.shields.io/badge/release-1.0.9-red.svg?style=flat) **Android Bitmap Monitor** 是一个 Android 图片内存分析工具,可以帮助开发者快速发现应用的图片使用是否合理,支持在线下和线上使用。 @@ -20,7 +20,8 @@ |版本|变更| 备注 | |---|---| --- | -|1.0.8|修复使用 Glide 加载的图片,还原时可能为纯黑的问题;支持 no-op 依赖(感谢 [yibaoshan](https://github.com/yibaoshan))|此版本有问题,mavenCentral 服务问题新版本暂未升级成功,请先使用 1.0.7| +|1.0.9|优化性能,减少主线程耗时|| +|1.0.8|修复使用 Glide 加载的图片,还原时可能为纯黑的问题;支持 no-op 依赖(感谢 [yibaoshan](https://github.com/yibaoshan))|此版本性能欠佳,建议使用 1.0.9| |1.0.7|完善悬浮窗和图片列表功能,修复悬浮窗可能出现多个的问题|| ## 功能介绍 @@ -86,11 +87,11 @@ android { dependencies { //依赖方式 1,如果线上线下都要使用,可以通过以下方式依赖 - implementation 'io.github.shixinzhang:android-bitmap-monitor:1.0.8' + implementation 'io.github.shixinzhang:android-bitmap-monitor:1.0.9' //依赖方式 2,如果不希望正式包中有代码运行,可以通过以下方式依赖 - releaseImplementation 'io.github.shixinzhang:android-bitmap-monitor-no-op:1.0.8' - debugImplementation 'io.github.shixinzhang:android-bitmap-monitor:1.0.8' + releaseImplementation 'io.github.shixinzhang:android-bitmap-monitor-no-op:1.0.9' + debugImplementation 'io.github.shixinzhang:android-bitmap-monitor:1.0.9' } ``` diff --git a/app/build.gradle b/app/build.gradle index ddf1f49..1955872 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,12 +42,12 @@ dependencies { // debugImplementation project(path: ':library') // releaseImplementation project(path: ':library-no-op') - debugImplementation 'io.github.shixinzhang:android-bitmap-monitor:1.0.8' - releaseImplementation 'io.github.shixinzhang:android-bitmap-monitor-no-op:1.0.8' + debugImplementation 'io.github.shixinzhang:android-bitmap-monitor:1.0.9' + releaseImplementation 'io.github.shixinzhang:android-bitmap-monitor-no-op:1.0.9' // implementation project(path: ':library') -// implementation 'io.github.shixinzhang:android-bitmap-monitor:1.0.8' -// implementation 'io.github.shixinzhang:android-bitmap-monitor-no-op:1.0.8' +// implementation 'io.github.shixinzhang:android-bitmap-monitor:1.0.9' +// implementation 'io.github.shixinzhang:android-bitmap-monitor-no-op:1.0.9' implementation 'com.github.bumptech.glide:glide:4.14.2' annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2' diff --git a/library-no-op/build.gradle b/library-no-op/build.gradle index e5a4f95..d466699 100644 --- a/library-no-op/build.gradle +++ b/library-no-op/build.gradle @@ -69,7 +69,7 @@ if (project.hasProperty("uploadToMavenCentral") && project.property("uploadToMav groupId "io.github.shixinzhang" artifactId "android-bitmap-monitor-no-op" - version "1.0.8" + version "1.0.9" afterEvaluate { from components.release diff --git a/library/build.gradle b/library/build.gradle index a4a13d3..31a23bb 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -87,7 +87,7 @@ if (project.hasProperty("uploadToMavenCentral") && project.property("uploadToMav groupId "io.github.shixinzhang" artifactId "android-bitmap-monitor" - version "1.0.8" + version "1.0.9" afterEvaluate { from components.release diff --git a/library/src/main/cpp/bitmap_monitor.cpp b/library/src/main/cpp/bitmap_monitor.cpp index 044938f..b2b3008 100644 --- a/library/src/main/cpp/bitmap_monitor.cpp +++ b/library/src/main/cpp/bitmap_monitor.cpp @@ -41,6 +41,13 @@ bool restore_image(JNIEnv *env, jobject bitmap_obj, unsigned int width, unsigned if (env == nullptr || bitmap_obj == nullptr || copy_file_path == nullptr) { return false; } + jboolean object_recycled = env->IsSameObject(bitmap_obj, nullptr); + + if (object_recycled == JNI_TRUE) { + //not reachable + return false; + } + void *pixels; if (AndroidBitmap_lockPixels(env, bitmap_obj, &pixels) == 0) { LOGI("restore_image, width: %d, height: %d, %s", width, height, copy_file_path); @@ -392,12 +399,12 @@ jint do_hook_bitmap(long bitmap_recycle_check_interval, } extern "C" -jobject do_dump_info(JNIEnv *env, bool justCount) { - jclass bitmap_record_class = env->FindClass( - "top/shixinzhang/bitmapmonitor/BitmapRecord"); +jobject do_dump_info(JNIEnv *env, bool justCount, bool ensureRestoreImage) { - jmethodID bitmap_record_constructor_method = env->GetMethodID(bitmap_record_class, "", - "(JIIIIJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + + if (g_ctx.java_vm != nullptr) { + g_ctx.java_vm->AttachCurrentThread(&env, nullptr); + } if (!g_ctx.record_mutex.try_lock()) { return nullptr; @@ -408,7 +415,7 @@ jobject do_dump_info(JNIEnv *env, bool justCount) { long long remain_bitmap_size = 0; int index = 0; jobjectArray java_bitmap_record_array = env->NewObjectArray(remain_bitmap_count, - bitmap_record_class, nullptr); + g_ctx.bitmap_record_class, nullptr); for (auto record : bitmap_records) { remain_bitmap_size += record.height * record.stride; @@ -420,11 +427,7 @@ jobject do_dump_info(JNIEnv *env, bool justCount) { jstring current_scene = record.current_scene; bool restore_succeed = record.restore_succeed; - if (g_ctx.java_vm != nullptr) { - g_ctx.java_vm->AttachCurrentThread(&env, nullptr); - } - - if (save_path != nullptr && !restore_succeed) { + if (ensureRestoreImage && save_path != nullptr && !restore_succeed) { //Need get pixels and restore again char *path = const_cast(env->GetStringUTFChars(save_path, 0)); unsigned int bit_per_pixel = record.stride / record.width; @@ -436,8 +439,8 @@ jobject do_dump_info(JNIEnv *env, bool justCount) { //每一条记录 jobject java_record = env->NewObject( - bitmap_record_class, - bitmap_record_constructor_method, + g_ctx.bitmap_record_class, + g_ctx.bitmap_record_constructor_method, (jlong) record.native_ptr, (jint) record.width, (jint) record.height, @@ -520,6 +523,16 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { jclass bitmap_jclass = env->FindClass("android/graphics/Bitmap"); g_ctx.native_ptr_field = env->GetFieldID(bitmap_jclass, "mNativePtr", "J"); + jclass bitmap_record_clz = env->FindClass( + "top/shixinzhang/bitmapmonitor/BitmapRecord"); + g_ctx.bitmap_record_class = (jclass)env->NewGlobalRef(bitmap_record_clz); + + if (g_ctx.bitmap_record_class != nullptr) { + g_ctx.bitmap_record_constructor_method = env->GetMethodID(g_ctx.bitmap_record_class, + "", + "(JIIIIJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + } + g_ctx.dump_stack_method = dump_stack_method_id; g_ctx.get_current_scene_method = get_current_scene_method_id; g_ctx.inited = true; @@ -554,11 +567,11 @@ Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_dumpBitmapCountNative(JNIEnv *e if (!g_ctx.open_hook) { return nullptr; } - return do_dump_info(env, true); + return do_dump_info(env, true, false); } extern "C" JNIEXPORT jobject JNICALL -Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_dumpBitmapInfoNative(JNIEnv *env, jclass clazz) { - return do_dump_info(env, false); +Java_top_shixinzhang_bitmapmonitor_BitmapMonitor_dumpBitmapInfoNative(JNIEnv *env, jclass clazz, jboolean ensureRestoreImage) { + return do_dump_info(env, false, ensureRestoreImage); } \ No newline at end of file diff --git a/library/src/main/cpp/bitmap_monitor.h b/library/src/main/cpp/bitmap_monitor.h index 978088a..004d55b 100644 --- a/library/src/main/cpp/bitmap_monitor.h +++ b/library/src/main/cpp/bitmap_monitor.h @@ -47,11 +47,16 @@ struct BitmapMonitorContext { jmethodID bitmap_recycled_method; jclass bitmap_monitor_jclass; jfieldID native_ptr_field; + jmethodID dump_stack_method; jmethodID get_current_scene_method; + jclass bitmap_info_jclass; jmethodID report_bitmap_data_method; + jclass bitmap_record_class; + jmethodID bitmap_record_constructor_method; + std::vector bitmap_records; int64_t create_bitmap_count; diff --git a/library/src/main/java/top/shixinzhang/bitmapmonitor/BitmapMonitor.java b/library/src/main/java/top/shixinzhang/bitmapmonitor/BitmapMonitor.java index f90ce0c..950c4bc 100644 --- a/library/src/main/java/top/shixinzhang/bitmapmonitor/BitmapMonitor.java +++ b/library/src/main/java/top/shixinzhang/bitmapmonitor/BitmapMonitor.java @@ -12,6 +12,7 @@ import java.util.List; import androidx.annotation.Keep; +import androidx.annotation.WorkerThread; import top.shixinzhang.bitmapmonitor.ui.FloatWindow; @@ -112,8 +113,14 @@ public static void toggleFloatWindowVisibility(boolean show) { /** * 获取这段期间 hook 的 bitmap 数据 (包括总数和具体各个图片的信息) */ + @WorkerThread public static BitmapMonitorData dumpBitmapInfo() { - return dumpBitmapInfoNative(); + return dumpBitmapInfoNative(false); + } + + @WorkerThread + public static BitmapMonitorData dumpBitmapInfo(boolean ensureRestoreImage) { + return dumpBitmapInfoNative(true); } /** @@ -233,12 +240,12 @@ public static Config getConfig() { private static native BitmapMonitorData dumpBitmapCountNative(); /** - * 获取数量和堆栈信息 - * + * Get all bitmap info + * @param ensureRestoreImage whether need check and restore again * @return */ @Keep - private static native BitmapMonitorData dumpBitmapInfoNative(); + private static native BitmapMonitorData dumpBitmapInfoNative(boolean ensureRestoreImage); public static class Config { //检查 Bitmap 是否回收的间隔,单位:秒 diff --git a/library/src/main/java/top/shixinzhang/bitmapmonitor/ui/BitmapRecordsActivity.java b/library/src/main/java/top/shixinzhang/bitmapmonitor/ui/BitmapRecordsActivity.java index abf98a1..42ff4c4 100644 --- a/library/src/main/java/top/shixinzhang/bitmapmonitor/ui/BitmapRecordsActivity.java +++ b/library/src/main/java/top/shixinzhang/bitmapmonitor/ui/BitmapRecordsActivity.java @@ -6,6 +6,8 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -53,11 +55,33 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { tvSortBySize.setOnClickListener(this); tvSortByTime.setOnClickListener(this); - bindData(); + requestData(); + } + + private void requestData() { + new Thread(new Runnable() { + @Override + public void run() { + data = BitmapMonitor.dumpBitmapInfo(true); + if (data == null) { + return; + } + + bindDataInMainThread(); + } + }).start(); + } + + private void bindDataInMainThread() { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + bindData(); + } + }); } private void bindData() { - data = BitmapMonitor.dumpBitmapInfo(); if (data == null) { return; }