30分钟带你深入优化安卓Bitmap大图

2024-01-20 04:36

本文主要是介绍30分钟带你深入优化安卓Bitmap大图,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

30分钟带你源码深入了解Bitmap以及优化安卓大图

  • 一、前言
  • 二、Bitmap入门
    • 1. 如何创建Bitmap?
    • 2. Bitmap的堆内存分布在哪里
    • 3. 图片文件越大,Bitmap堆内存会越大吗?
    • 4. 如何管理Bitmap的内存?
    • 5. 实战修改Bitmap的堆内存,改变图片的图案
      • 5.1 第一步,创建一个具备JNI能力的工程
      • 5.2 第二步, 加入图片显示到屏幕
      • 5.3 进入修改Bitmap环节
      • 5.4 Bitmap的堆内存分布格式
      • 5.5 修改Bitmap的堆内存

一、前言

众所周知,安卓的图片都是通过Bitmap来完成设置间接交给Gpu去渲染到屏幕。如果想要优化图片,你还是个入门者,那么就必须要了解Bitmap.接下来我们先介绍Bitmap,再去深入研究如何优化大图如何在App上显示

二、Bitmap入门

1. 如何创建Bitmap?

(1) Bitmap是通过Bitmap.createBitMap创建的

2. Bitmap的堆内存分布在哪里

通过源码,可以分析出,Bitmap的堆内存是分布在native.

2.1 创建Bitmap时序图
在这里插入图片描述
2.2 分析创建Bitmap代码
创建Bitmap第一步调用Bitmap的createBitmap,然后我们发下他会调用一个自身的重载方法createBitmap

 public static Bitmap createBitmap(int width, int height, @NonNull Config config,boolean hasAlpha, @NonNull ColorSpace colorSpace) {return createBitmap(null, width, height, config, hasAlpha, colorSpace);}

然后,这个重载方法会通过调用nativeCreate调用JNI来创建Bitmap,并在Native分配堆内存


public static Bitmap createBitmap(@Nullable DisplayMetrics display, int width, int height,@NonNull Config config, boolean hasAlpha, @NonNull ColorSpace colorSpace) {//...省略Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true,colorSpace == null ? 0 : colorSpace.getNativeInstance());//...省略               return bm;
}

2.3 分配内存
接下来我们揭秘,Bitmap到底是怎样在Native分配内存的。
我们下载安卓的源码,nativeCreate对于的JNI的C++代码在Bitmap.h
路径在这: /frameworks/base/core/jni/android/graphics/Bitmap.h
并找到gBitmapMethods这个属性


static const JNINativeMethod gBitmapMethods[] = {
1577    {   "nativeCreate",             "([IIIIIIZ[FLandroid/graphics/ColorSpace$Rgb$TransferParameters;)Landroid/graphics/Bitmap;",
1578        (void*)Bitmap_creator },
//..省略
}

于是我们可以发现,Java中BitmapnativeCreate方法执行的方法对应是Bitmap.hBitmap_creator方法。

static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,
653                              jint offset, jint stride, jint width, jint height,
654                              jint configHandle, jboolean isMutable,
655                              jfloatArray xyzD50, jobject transferParameters) {669    ///省略...//创建SkBitmap引用
670    SkBitmap bitmap;///省略...// 设置初始化参数
681    bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, colorSpace));// 分配Bitmap堆内存
683    sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap);///省略...
693
694    return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable));
695}
696

由此可见,Bitmap内存是分配在Native.

在这里插入图片描述

3. 图片文件越大,Bitmap堆内存会越大吗?

其实,从上面源码我们就已经得出结论了,不管图片是1M还是2M,只要按一样的参数设置,总内存不会变大。

SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, colorSpace)

因为图片的显示只有宽,高,图片格式(rgb888之类),透明度方式,颜色空间这些参数。并没有文件大小相关这样的设定。

如何测试?可以使用Android StudioProfiler查看App内存分布,设置图片后,内存此刻变化较大的那部分增量的内存肯定就是图片的内存
App内存分布
下面是参数的解释

  • code: 虚拟机方法区代码的大小
  • stack:虚拟机栈内存,如果这里数据越大,波动越大,就代表CPU越活跃,不停在运行
  • graphics: Opengl 操作的GPU内存大小
  • native: native的堆内存,也就是运行c/c++的内存
  • Java: java 的堆内存大小

当我们设置图片后,Native的堆内存就会增大。有兴趣的话可以去试试

4. 如何管理Bitmap的内存?

在执行Bitmap.createBitmap时候,我们从上面得知,会执行nativeBitmap_creator,然后Bitmap_creator会执行navtiveBitmapcreateBitmap*.从而去创建一个BitmapWrapper对象,这个对象会持有Bitmap对象。最终将BitmapWrapper这个对象指针作为入参,通过JNI创建一个 Java的Bitmap对象让其持有BitmapWrapper ,每次Java操作Bitmap时候,都会通过这个指针传到Native层,然后转成BitmapWrapper 去管理内存的数据。

jobject createBitmap(JNIEnv* env, Bitmap* bitmap,
200        int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
201        int density) {//..省略//创建BitmapWrapper对象,这个对象会返回给Java持有
207    BitmapWrapper* bitmapWrapper = new BitmapWrapper(bitmap);//通过JNI创建Java的Bitmap对象//BitmapWrapper对象的指针引用作为入参传给的构造方法,、// 并创建Java的Bitmap对象返回到Java层
208    jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
209            reinterpret_cast<jlong>(bitmapWrapper), bitmap->width(), bitmap->height(), density,
210            isMutable, isPremultiplied, ninePatchChunk, ninePatchInsets);//..省略
216    return obj;
217}

Java Bitmap的构造方法

Bitmap(long nativeBitmap, int width, int height, int density,boolean requestPremultiplied, byte[] ninePatchChunk,NinePatch.InsetStruct ninePatchInsets, boolean fromMalloc) {mWidth = width;mHeight = height;//省略//将BitmapWrapper指针引用保存下来,通过他去管理native的bitmapmNativePtr = nativeBitmap;//省略}

举个例子,比如使用最常用的方法,回收内存。回收时候是通过把mNativePtr指针传到native,最终将其回收。

 public void recycle() {if (!mRecycled) {nativeRecycle(mNativePtr);mNinePatchChunk = null;mRecycled = true;mHardwareBuffer = null;}}
 {   "nativeRecycle",            "(J)Z", (void*)Bitmap_recycle },static jboolean Bitmap_recycle(JNIEnv* env, jobject, jlong bitmapHandle) {
852    LocalScopedBitmap bitmap(bitmapHandle);
853    bitmap->freePixels();
854    return JNI_TRUE;
855}

5. 实战修改Bitmap的堆内存,改变图片的图案

简单需求:修改需要显示的Bitmap数据,修改图片左上方1/4的区域改为红色.

5.1 第一步,创建一个具备JNI能力的工程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
创建后主要有4个文件,分别为

  • MainAcitivity.java
  • activity_main.xml
  • CMakeLists.txt
  • native-lib.cpp

MainAcitivity.java

package com.example.myapplication;
public class MainActivity extends AppCompatActivity {// Used to load the 'myapplication' library on application startup.static {System.loadLibrary("myapplication");}private ActivityMainBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());// Example of a call to a native methodTextView tv = binding.sampleText;tv.setText(stringFromJNI());}/*** A native method that is implemented by the 'myapplication' native library,* which is packaged with this application.*/public native String stringFromJNI();
}

native-lib.cpp

#include <jni.h>
#include <string>
#include <android/bitmap.h>
#include <android/log.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv* env,jobject ) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}

CMakeLists.txt

add_library(${CMAKE_PROJECT_NAME} SHARED# List C/C++ source files with relative paths to this CMakeLists.txtnative-lib.cpp) target_link_libraries(${CMAKE_PROJECT_NAME}# List libraries link to the target libraryandroidjnigraphicslog)

我们不做任何修改, 先运行起来,默认的Demo是中间有个Text,显示Native的文字“Hello from C++”

在这里插入图片描述

5.2 第二步, 加入图片显示到屏幕

修改xml,加入ImageView,拖到这个文字的正上方,宽高各自设置为200,然后 加入一张备好的名为king.jpg图片放到asset目录。

在这里插入图片描述
在这里插入图片描述

通过AssetManagerAssetManager,将这个king.jpg显示到ImageView上

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());// Example of a call to a native methodTextView tv = binding.sampleText;tv.setText(stringFromJNI());new Thread(new Runnable() {@Overridepublic void run() {AssetManager assetManager = getAssets();InputStream inputStream = null;try {//从asset目录加载出king.jpg这个图inputStream = assetManager.open("king.jpg");Bitmap bitmap = BitmapFactory.decodeStream(inputStream);//切回UI线程,把bitmap设置到ImageView显示出来runOnUiThread(new Runnable() {@Overridepublic void run() {ImageView imageView = binding.imageView;imageView.setImageBitmap(bitmap);}});} catch (IOException e) {throw new RuntimeException(e);}}}).start();}

运行效果如下:
在这里插入图片描述

5.3 进入修改Bitmap环节

首先,我们创建一个类BitmapHelper.java ,通过他来调用JNI接口修改Bitmap,对应的方法是updateBitmap

package com.example.myapplication;import android.graphics.Bitmap;public class BitmapHelper {public native void updateBitmap(Bitmap bitmap);
}

在修改之前,我们先了解c++怎么获取Java的bitmap宽高参数。 他是通过头文件android/bitmap.h的这个AndroidBitmap_getInfo函数获取,并且赋值到AndroidBitmapInfo这个结构体. AndroidBitmapInfo是在安卓源码的这个路径/frameworks/native/include/android/bitmap.h

65/** Bitmap info, see AndroidBitmap_getInfo(). */
66typedef struct {
67    /** The bitmap width in pixels. */
68    uint32_t    width; //图片的宽
69    /** The bitmap height in pixels. */
70    uint32_t    height;//图片的高
71    /** The number of byte per row. */
72    uint32_t    stride;//每一行的字节数,如果是ARGB_8888格式,这个stride/4正好等价于图片的width,因为一个像素点占4个字节,分别为透明度,蓝, 绿 , 红(这里是和平时的位置是倒过来的)
73    /** The bitmap pixel format. See {@link AndroidBitmapFormat} */
74    int32_t     format;//图片格式
75    /** Unused. */
76    uint32_t    flags;      // 0 for now
77} AndroidBitmapInfo;

操作Bitmap系列的方法需要导入jnigraphics这个库, 在CmakeList.txt配置即可,Android Studio编译时候会把这个库导入到我们的环境。修改如下,在target_link_libraries加入一个jnigraphics

CmakeList.txt

target_link_libraries(${CMAKE_PROJECT_NAME}# List libraries link to the target libraryandroidjnigraphicslog)

我们来到native-lib.cpp, 加入BitmapHelperupdateBitmap对应的native方法

Java_com_example_myapplication_BitmapHelper_updateBitmap
并且导入android/bitmap.handroid/log.h
通过AndroidBitmap_getInfo, 获取图片宽高,打印出来。代码如下

#include <jni.h>
#include <string>
#include <android/bitmap.h>
#include <android/log.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(JNIEnv* env,jobject ) {std::string hello = "关注King老师,3连哦";return env->NewStringUTF(hello.c_str());
}extern "C" JNIEXPORT void JNICALL
Java_com_example_myapplication_BitmapHelper_updateBitmap(JNIEnv* env,
jobject, jobject bitmap) {//声明一个AndroidBitmapInfo,用来获取bitmap的参数信息AndroidBitmapInfo  info;// 将java的bitmap的宽高信息填充到AndroidBitmapInfoAndroidBitmap_getInfo(env, bitmap, &info);//这个方法就是打印安卓的日志方法, ANDROID_LOG_INFO对于就是info级别的日志, 然后第二个参数是tag//如果AndroidBitmap_getInfo获取成功, 打印图片的宽高就能出现__android_log_print(ANDROID_LOG_INFO, "updateBitmap", "width:%d, height:%d", info.width, info.height);
}

先运行起来,看看效果,就是文字改了下,你懂的。注意了,如果jnigraphics这个库你没有加入,肯定是编译失败的。
在这里插入图片描述
打印效果

在这里插入图片描述
打印出的宽高和上面设置的一致。

5.4 Bitmap的堆内存分布格式

他是通过二维数组存下来的,现在默认的是ARGB_8888格式,所以一个像素是有4个字节组成。
屏幕绘制一个图片时候,是从上到下一行一行的像素列点下来的。由于内存存储方式是顺序存储,内存是分布连续的,图片绘制完一行时候,下一行的位置即为指针加上宽度的偏移量。

在这里插入图片描述
从数组维度上面可以这样理解
在这里插入图片描述

5.5 修改Bitmap的堆内存

有了上面的理解,我们就可以修改bitmap堆内存了,如下。

extern "C" JNIEXPORT void JNICALL
Java_com_example_myapplication_BitmapHelper_updateBitmap(JNIEnv* env,
jobject, jobject bitmap) {//声明一个AndroidBitmapInfo,用来获取bitmap的参数信息AndroidBitmapInfo  info;// 将java的bitmap的宽高信息填充到AndroidBitmapInfoAndroidBitmap_getInfo(env, bitmap, &info);__android_log_print(ANDROID_LOG_INFO, "updateBitmap", "width:%d, height:%d", info.width, info.height);//声明一个指向二维数组内存的指针,也就是指向Bitmap内存的指针,初始化是第一行的位置int *displayRowIndex  = NULL;//当前行数组int *currentRowPixelsArray = NULL;int row = info.width;int column = info.height;//将bitmap的内存指针赋给displayRowIndex,并锁定内存AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void **>(&displayRowIndex));//只改上面一半的行数for (int i = 0; i < column/2; ++i) {currentRowPixelsArray = displayRowIndex;//只改左边一半的列for (int j = 0; j < row/2; ++j) {//这里的颜色和设计稿的颜色是反过来的,只有透明度位置没变,格式为0x+透明度+蓝+绿+红//比如红色0xFFFF0000,那么在堆内存存储是从右到左反过来,则为0xFF0000FFcurrentRowPixelsArray[j] = 0xFF0000FF;}//把索引偏移到下一行的位置displayRowIndex +=  info.width;}//解锁bitmap内存操作AndroidBitmap_unlockPixels(env, bitmap);
}

运行效果如下。
在这里插入图片描述
未完结,待更新,有时间会第一时间更新。请关注,3连

这篇关于30分钟带你深入优化安卓Bitmap大图的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/624801

相关文章

uniapp接入微信小程序原生代码配置方案(优化版)

uniapp项目需要把微信小程序原生语法的功能代码嵌套过来,无需把原生代码转换为uniapp,可以配置拷贝的方式集成过来 1、拷贝代码包到src目录 2、vue.config.js中配置原生代码包直接拷贝到编译目录中 3、pages.json中配置分包目录,原生入口组件的路径 4、manifest.json中配置分包,使用原生组件 5、需要把原生代码包里的页面修改成组件的方

Python应用开发——30天学习Streamlit Python包进行APP的构建(9)

st.area_chart 显示区域图。 这是围绕 st.altair_chart 的语法糖。主要区别在于该命令使用数据自身的列和指数来计算图表的 Altair 规格。因此,在许多 "只需绘制此图 "的情况下,该命令更易于使用,但可定制性较差。 如果 st.area_chart 无法正确猜测数据规格,请尝试使用 st.altair_chart 指定所需的图表。 Function signa

服务器雪崩的应对策略之----SQL优化

SQL语句的优化是数据库性能优化的重要方面,特别是在处理大规模数据或高频访问时。作为一个C++程序员,理解SQL优化不仅有助于编写高效的数据库操作代码,还能增强对系统性能瓶颈的整体把握。以下是详细的SQL语句优化技巧和策略: SQL优化 1. 选择合适的数据类型2. 使用索引3. 优化查询4. 范式化和反范式化5. 查询重写6. 使用缓存7. 优化数据库设计8. 分析和监控9. 调整配置1、

Java中如何优化数据库查询性能?

Java中如何优化数据库查询性能? 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将深入探讨在Java中如何优化数据库查询性能,这是提升应用程序响应速度和用户体验的关键技术。 优化数据库查询性能的重要性 在现代应用开发中,数据库查询是最常见的操作之一。随着数据量的增加和业务复杂度的提升,数据库查询的性能优化显得尤为重

PyTorch模型_trace实战:深入理解与应用

pytorch使用trace模型 1、使用trace生成torchscript模型2、使用trace的模型预测 1、使用trace生成torchscript模型 def save_trace(model, input, save_path):traced_script_model = torch.jit.trace(model, input)<

打包体积分析和优化

webpack分析工具:webpack-bundle-analyzer 1. 通过<script src="./vue.js"></script>方式引入vue、vuex、vue-router等包(CDN) // webpack.config.jsif(process.env.NODE_ENV==='production') {module.exports = {devtool: 'none

从《深入设计模式》一书中学到的编程智慧

软件设计原则   优秀设计的特征   在开始学习实际的模式前,让我们来看看软件架构的设计过程,了解一下需要达成目标与需要尽量避免的陷阱。 代码复用 无论是开发何种软件产品,成本和时间都最重要的两个维度。较短的开发时间意味着可比竞争对手更早进入市场; 较低的开发成本意味着能够留出更多营销资金,因此能更广泛地覆盖潜在客户。 代码复用是减少开发成本时最常用的方式之一。其意图

[大师C语言(第三十六篇)]C语言信号处理:深入解析与实战

引言 在计算机科学中,信号是一种软件中断,它允许进程之间或进程与内核之间进行通信。信号处理是操作系统中的一个重要概念,它允许程序对各种事件做出响应,例如用户中断、硬件异常和系统调用。C语言作为一门接近硬件的编程语言,提供了强大的信号处理能力。本文将深入探讨C语言信号处理的技术和方法,帮助读者掌握C语言处理信号的高级技巧。 第一部分:C语言信号处理基础 1.1 信号的概念 在Unix-lik

2024年6月24日-6月30日(ue独立游戏为核心)

试过重点放在独立游戏上,有个indienova独立游戏团队是全职的,由于他们干了几个月,节奏暂时跟不上,紧张焦虑了。五一时也有点自暴自弃了,实在没必要,按照自己的节奏走即可。精力和时间也有限,放在周末进行即可。除非哪天失业了,再也找不到工作了,再把重心放在独立游戏上。 另外,找到一个同样业余的美术,从头做肉鸽游戏,两周一次正式交流即可。节奏一定要放慢,不能影响正常工作生活。如果影响到了,还不如自

Clickhouse 的性能优化实践总结

文章目录 前言性能优化的原则数据结构优化内存优化磁盘优化网络优化CPU优化查询优化数据迁移优化 前言 ClickHouse是一个性能很强的OLAP数据库,性能强是建立在专业运维之上的,需要专业运维人员依据不同的业务需求对ClickHouse进行有针对性的优化。同一批数据,在不同的业务下,查询性能可能出现两极分化。 性能优化的原则 在进行ClickHouse性能优化时,有几条