保定市住房和城乡建设局网站,网站做排名需要多少钱,怎么买到精准客户的电话,建盏的好处30分钟带你源码深入了解Bitmap以及优化安卓大图 一、前言二、Bitmap入门1. 如何创建Bitmap?2. Bitmap的堆内存分布在哪里3. 图片文件越大#xff0c;Bitmap堆内存会越大吗#xff1f;4. 如何管理Bitmap的内存#xff1f;5. 实战修改Bitmap的堆内存#xff0c;改变图片的图… 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中Bitmap的nativeCreate方法执行的方法对应是Bitmap.h的Bitmap_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_spBitmap 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 Studio的Profiler查看App内存分布设置图片后内存此刻变化较大的那部分增量的内存肯定就是图片的内存。 下面是参数的解释
code: 虚拟机方法区代码的大小stack虚拟机栈内存如果这里数据越大波动越大就代表CPU越活跃不停在运行graphics: Opengl 操作的GPU内存大小native: native的堆内存也就是运行c/c的内存Java: java 的堆内存大小
当我们设置图片后Native的堆内存就会增大。有兴趣的话可以去试试
4. 如何管理Bitmap的内存
在执行Bitmap.createBitmap时候我们从上面得知会执行native的Bitmap_creator然后Bitmap_creator会执行navtive的Bitmap的createBitmap*.从而去创建一个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_castjlong(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.javaactivity_main.xmlCMakeLists.txtnative-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, 加入BitmapHelper的updateBitmap对应的native方法
Java_com_example_myapplication_BitmapHelper_updateBitmap 并且导入android/bitmap.h android/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_castvoid **(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连