Android中有没有必要调用Bitmap的recycle()


    这篇博客时转载其他人的,具体我也忘记了。。。。


在Android应用里,最耗费内存的就是图片资源。而且在Android系统中,读取位图Bitmap时,分给虚拟机中的图片的堆栈大小只有8M,如果超出了,就会出现OutOfMemory异常。所以,对于图片的内存优化,是Android应用开发中比较重要的内容。

  1) 要及时回收Bitmap的内存

  Bitmap类有一个方法recycle(),从方法名可以看出意思是回收。这里就有疑问了,Android系统有自己的垃圾回收机制,可以不定期的回收掉不使用的内存空间,当然也包括Bitmap的空间。那为什么还需要这个方法呢?

  Bitmap类的构造方法都是私有的,所以开发者不能直接new出一个Bitmap对象,只能通过BitmapFactory类的各种静态方法来实例化一个Bitmap。仔细查看BitmapFactory的源代码可以看到,生成Bitmap对象最终都是通过JNI调用方式实现的。所以,加载Bitmap到内存里以后,是包含两部分内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了,但是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。所以需要调用recycle()方法来释放C部分的内存。从Bitmap类的源代码也可以看到,recycle()方法里也的确是调用了JNI方法了的

  那如果不调用recycle(),是否就一定存在内存泄露呢?也不是的。Android的每个应用都运行在独立的进程里,有着独立的内存,如果整个进程被应用本身或者系统杀死了,内存也就都被释放掉了,当然也包括C部分的内存。

  Android对于进程的管理是非常复杂的。简单的说,Android系统的进程分为几个级别,系统会在内存不足的情况下杀死一些低优先级的进程,以提供给其它进程充足的内存空间。在实际项目开发过程中,有的开发者会在退出程序的时候使用Process.killProcess(Process.myPid())的方式将自己的进程杀死,但是有的应用仅仅会使用调用Activity.finish()方法的方式关闭掉所有的Activity。

  经验分享:

  Android手机的用户,根据习惯不同,可能会有两种方式退出整个应用程序:一种是按Home键直接退到桌面;另一种是从应用程序的退出按钮或者按Back键退出程序。那么从系统的角度来说,这两种方式有什么区别呢?按Home键,应用程序并没有被关闭,而是成为了后台应用程序。按Back键,一般来说,应用程序关闭了,但是进程并没有被杀死,而是成为了空进程(程序本身对退出做了特殊处理的不考虑在内)。

  Android系统已经做了大量进程管理的工作,这些已经可以满足用户的需求。个人建议,应用程序在退出应用的时候不需要手动杀死自己所在的进程。对于应用程序本身的进程管理,交给Android系统来处理就可以了。应用程序需要做的,是尽量做好程序本身的内存管理工作。

  一般来说,如果能够获得Bitmap对象的引用,就需要及时的调用Bitmap的recycle()方法来释放Bitmap占用的内存空间,而不要等Android系统来进行释放。

  下面是释放Bitmap的示例代码片段。

  // 先判断是否已经回收

  if(bitmap != null && !bitmap.isRecycled()){

  // 回收并且置为null

  bitmap.recycle();

  bitmap = null;

  }

  System.gc();

  从上面的代码可以看到,bitmap.recycle()方法用于回收该Bitmap所占用的内存,接着将bitmap置空,最后使用System.gc()调用一下系统的垃圾回收器进行回收,可以通知垃圾回收器尽快进行回收。这里需要注意的是,调用System.gc()并不能保证立即开始进行回收过程,而只是为了加快回收的到来

  如何调用recycle()方法进行回收已经了解了,那什么时候释放Bitmap的内存比较合适呢?一般来说,如果代码已经不再需要使用Bitmap对象了,就可以释放了。释放内存以后,就不能再使用该Bitmap对象了,如果再次使用,就会抛出异常。所以一定要保证不再使用的时候释放。比如,如果是在某个Activity中使用Bitmap,就可以在Activity的onStop()或者onDestroy()方法中进行回收。

  2) 捕获异常

  因为Bitmap是吃内存大户,为了避免应用在分配Bitmap内存的时候出现OutOfMemory异常以后Crash掉,需要特别注意实例化Bitmap部分的代码。通常,在实例化Bitmap的代码中,一定要对OutOfMemory异常进行捕获。

  以下是代码示例。

  Bitmap bitmap = null;

  try {

  // 实例化Bitmap

  bitmap = BitmapFactory.decodeFile(path);

  } catch (OutOfMemoryError e) {

  //

  }

  if (bitmap == null) {

  // 如果实例化失败 返回默认的Bitmap对象

  return defaultBitmapMap;

  }

  这里对初始化Bitmap对象过程中可能发生的OutOfMemory异常进行了捕获。如果发生了OutOfMemory异常,应用不会崩溃,而是得到了一个默认的Bitmap图。

  经验分享:

  很多开发者会习惯性的在代码中直接捕获Exception。但是对于OutOfMemoryError来说,这样做是捕获不到的。因为OutOfMemoryError是一种Error,而不是Exception。在此仅仅做一下提醒,避免写错代码而捕获不到OutOfMemoryError。

  3) 缓存通用的Bitmap对象

  有时候,可能需要在一个Activity里多次用到同一张图片。比如一个Activity会展示一些用户的头像列表,而如果用户没有设置头像的话,则会显示一个默认头像,而这个头像是位于应用程序本身的资源文件中的。

  如果有类似上面的场景,就可以对同一Bitmap进行缓存。如果不进行缓存,尽管看到的是同一张图片文件,但是使用BitmapFactory类的方法来实例化出来的Bitmap,是不同的Bitmap对象。缓存可以避免新建多个Bitmap对象,避免内存的浪费。

  经验分享:

  Web开发者对于缓存技术是很熟悉的。其实在Android应用开发过程中,也会经常使用缓存的技术。这里所说的缓存有两个级别,一个是硬盘缓存,一个是内存缓存。比如说,在开发网络应用过程中,可以将一些从网络上获取的数据保存到SD卡中,下次直接从SD卡读取,而不从网络中读取,从而节省网络流量。这种方式就是硬盘缓存。再比如,应用程序经常会使用同一对象,也可以放到内存中缓存起来,需要的时候直接从内存中读取。这种方式就是内存缓存。

  4) 压缩图片

  如果图片像素过大,使用BitmapFactory类的方法实例化Bitmap的过程中,需要大于8M的内存空间,就必定会发生OutOfMemory异常。这个时候该如何处理呢?如果有这种情况,则可以将图片缩小,以减少载入图片过程中的内存的使用,避免异常发生。

  使用BitmapFactory.Options设置inSampleSize就可以缩小图片。属性值inSampleSize表示缩略图大小为原始图片大小的几分之一。即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片的大小就为原始大小的1/4。

  如果知道图片的像素过大,就可以对其进行缩小。那么如何才知道图片过大呢?

  使用BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方法,并不会真正的分配空间,即解码出来的Bitmap为null,但是可计算出原始图片的宽度和高度,即options.outWidth和options.outHeight。通过这两个值,就可以知道图片是否过大了。

  BitmapFactory.Options opts = new BitmapFactory.Options();

  // 设置inJustDecodeBounds为true

  opts.inJustDecodeBounds = true;

  // 使用decodeFile方法得到图片的宽和高

  BitmapFactory.decodeFile(path, opts);

  // 打印出图片的宽和高

  Log.d("example", opts.outWidth + "," + opts.outHeight);

  在实际项目中,可以利用上面的代码,先获取图片真实的宽度和高度,然后判断是否需要跑缩小。如果不需要缩小,设置inSampleSize的值为1。如果需要缩小,则动态计算并设置inSampleSize的值,对图片进行缩小。需要注意的是,在下次使用BitmapFactory的decodeFile()等方法实例化Bitmap对象前,别忘记将opts.inJustDecodeBound设置回false。否则获取的bitmap对象还是null。

  经验分享:

  如果程序的图片的来源都是程序包中的资源,或者是自己服务器上的图片,图片的大小是开发者可以调整的,那么一般来说,就只需要注意使用的图片不要过大,并且注意代码的质量,及时回收Bitmap对象,就能避免OutOfMemory异常的发生。

  如果程序的图片来自外界,这个时候就特别需要注意OutOfMemory的发生。一个是如果载入的图片比较大,就需要先缩小;另一个是一定要捕获异常,避免程序Crash。

<think>我们正在讨论的是在Android平台上使用Java调用GPU来渲染Bitmap图片。根据引用[1]和[2],我们知道Android默认使用GPU进行绘制,并且通过DisplayList将布局转换为GPU可识别的对象。但是,用户希望直接使用GPU来渲染一个Bitmap图片,这通常意味着我们需要利用GPU的加速能力来绘制位图。在Android中,通常我们通过自定义View并重写onDraw方法,然后使用Canvas绘制Bitmap,系统会自动使用GPU加速(如果硬件加速开启)。但是,如果用户想要更直接地控制GPU渲染,可能需要使用更底层的API,如OpenGL ES。 然而,根据引用[3],我们也可以知道GPU还可以用于通用计算任务,但这里用户的需求是“显示”Bitmap图片,所以主要是图形渲染。 考虑到用户要求使用Java(而不是NDK或C++)来实现,我们可以考虑以下两种主要方法:1.**使用Canvas和硬件加速**:这是最简单的方法,在硬件加速开启的情况下,Canvas的绘制操作会自动使用GPU。我们可以创建一个自定义View,在onDraw方法中绘制Bitmap。2.**使用OpenGLES**:通过OpenGLES可以更直接地控制GPU进行渲染。但是,使用OpenGLES需要更多的代码和知识。由于用户的问题中提到了“调用GPU渲染”,并且希望是Java方法,我们首先考虑第一种方法,因为它是Android默认的硬件加速方式,而且简单。同时,我们也可以介绍第二种方法作为更底层的控制。 ###方法1:使用Canvas和硬件加速(推荐) 在Android中,默认情况下,应用程序的UI是硬件加速的(从API14开始,默认开启)。我们可以在自定义View的onDraw方法中使用Canvas.drawBitmap()来绘制Bitmap,这样就会利用GPU进行加速。 步骤: 1.确保硬件加速开启(默认开启,但也可以检查)。 2.创建一个自定义View。3.重写onDraw方法,使用Canvas绘制Bitmap。示例代码: ```javapublic classCustomImageView extendsView {privateBitmapmBitmap;public CustomImageView(Contextcontext,Bitmapbitmap){super(context);mBitmap= bitmap;}@Overrideprotected voidonDraw(Canvascanvas){super.onDraw(canvas);//绘制Bitmap,这个操作在硬件加速开启时会使用GPUcanvas.drawBitmap(mBitmap,0,0, null);} }``` 然后,在Activity中使用这个自定义View:```java//假设你有一个Bitmap对象Bitmap bitmap= ...; //你的BitmapCustomImageView imageView =new CustomImageView(this, bitmap); setContentView(imageView);``` 注意:默认情况下,硬件加速是开启的。如果需要检查,可以在AndroidManifest.xml中为application或activity设置`android:hardwareAccelerated="true"`(默认就是true)。###方法2:使用OpenGLES(更底层) 如果用户需要更直接的控制,比如进行复杂的图像处理后再显示,那么可以使用OpenGLES。但是,使用OpenGLES渲染Bitmap需要以下步骤: 1.创建OpenGL ES环境(通常使用GLSurfaceView)。2.Bitmap转换为OpenGL纹理。 3.编写着色器程序,绘制一个矩形并贴上纹理。这种方法较为复杂,但可以提供更高的灵活性和性能,特别是在需要实时处理图像时。示例步骤(简化):-创建一个GLSurfaceView并设置渲染器。 -在渲染器中,将Bitmap加载为纹理。-在onDrawFrame中绘制纹理。由于代码较长,这里只给出关键步骤的代码框架:```javapublicclass MyGLRendererimplements GLSurfaceView.Renderer {privateint mTextureId;private Bitmap mBitmap;publicMyGLRenderer(Bitmapbitmap){mBitmap= bitmap;}@Overridepublic voidonSurfaceCreated(GL10 gl, EGLConfigconfig){//创建纹理并绑定int[] textures= newint[1];GLES20.glGenTextures(1, textures,0);mTextureId= textures[0];//绑定纹理GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId);//设置纹理参数GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR);GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);//将Bitmap加载到纹理GLUtils.texImage2D(GLES20.GL_TEXTURE_2D,0,mBitmap,0);}@Overridepublicvoid onDrawFrame(GL10gl){//清除屏幕GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);//使用着色器程序,绑定纹理,绘制矩形(这里省略了着色器程序和顶点坐标等细节)// ...具体绘制代码}// ...其他方法} ```然后,在Activity中设置GLSurfaceView:```javaGLSurfaceViewglSurfaceView =new GLSurfaceView(this);glSurfaceView.setEGLContextClientVersion(2);//使用OpenGL ES2.0MyGLRendererrenderer= newMyGLRenderer(bitmap); //传入你的BitmapglSurfaceView.setRenderer(renderer);setContentView(glSurfaceView); ```注意:使用OpenGL ES需要一定的学习成本,并且如果只是简单地显示图片,方法1更简单高效。 ###总结-对于简单的显示需求,使用自定义View和Canvas绘制Bitmap,利用系统默认的硬件加速(GPU)即可。 -对于需要更复杂控制或实时图像处理的情况,可以考虑使用OpenGLES。###相关问题1.如何在Android中检查硬件加速是否开启? 2.使用OpenGL ES渲染Bitmap时,如何处理不同的屏幕尺寸和比例? 3.除了OpenGL ES,还有哪些方法可以在Android中直接调用GPU进行图像处理?</think>### Android Java 调用 GPU 渲染 Bitmap 的方法 在 Android 中,GPU 渲染 Bitmap 主要通过硬件加速实现。以下是详细步骤和代码示例: --- #### **一、核心原理** 1. **硬件加速机制** Android 系统默认使用 GPU 进行视图渲染(从 API 14 开始默认开启)[^1]。当你在 `Canvas` 上绘制 Bitmap 时,系统会自动通过 **Display List** 将绘制指令转换为 GPU 可执行的操作[^2]。 2. **关键流程** ```mermaid graph LR A[Bitmap对象] --> B[Canvas绘制指令] B --> C[Display List转换] C --> D[GPU渲染] D --> E[屏幕显示] ``` --- #### **二、实现步骤与代码** ##### 方法 1:通过自定义 View 渲染(推荐) ```java public class GPURenderView extends View { private Bitmap mBitmap; public GPURenderView(Context context, Bitmap bitmap) { super(context); mBitmap = bitmap; // 确保开启硬件加速(默认开启) setLayerType(LAYER_TYPE_HARDWARE, null); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // GPU 加速绘制 Bitmap canvas.drawBitmap(mBitmap, 0, 0, null); } } ``` **使用方式**: ```java // 在 Activity 中 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.your_image); GPURenderView renderView = new GPURenderView(this, bitmap); setContentView(renderView); ``` ##### 方法 2:直接操作 TextureView(适用于动态内容) ```java TextureView textureView = findViewById(R.id.texture_view); textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // 创建 GPU 渲染的 Canvas Canvas canvas = textureView.lockCanvas(); canvas.drawBitmap(bitmap, 0, 0, null); textureView.unlockCanvasAndPost(canvas); // 提交到 GPU } // ... 其他回调方法 }); ``` --- #### **三、关键配置** 1. **强制开启硬件加速** 在 `AndroidManifest.xml` 中全局启用: ```xml <application android:hardwareAccelerated="true"> ``` 2. **检查加速状态** 代码中验证是否启用 GPU: ```java if (view.isHardwareAccelerated()) { // GPU 加速已启用 } ``` --- #### **四、性能优化建议** 1. **Bitmap 预处理** - 使用 `BitmapFactory.Options.inPreferredConfig = Bitmap.Config.HARDWARE`(API 26+)[^3] - 避免主线程解码大图 2. **减少 Overdraw** 通过 `canvas.clipRect()` 限制绘制区域,避免 GPU 渲染不可见内容。 3. **异步渲染** 复杂场景使用 `RenderNode` 和 `RecordingCanvas`(API 29+): ```java RenderNode renderNode = new RenderNode("bitmapRenderer"); RecordingCanvas canvas = renderNode.beginRecording(); canvas.drawBitmap(bitmap, 0, 0, null); renderNode.endRecording(); // 提交到 View view.getHardwareRenderer().drawRenderNode(renderNode); ``` --- #### **五、常见问题解决** - **图片模糊**:确保 Bitmap 分辨率与 View 尺寸匹配,避免缩放。 - **内存溢出**:使用 `Bitmap.recycle()` 及时释放资源。 - **兼容性问题**: 低于 API 14 的设备需在 View 构造器中调用: ```java setLayerType(View.LAYER_TYPE_SOFTWARE, null); // 关闭 GPU 加速 ``` --- ### 相关问题 1. 如何检测 Android 设备的 GPU 渲染性能? 2. 使用 OpenGL ES 直接渲染 Bitmap 与 Canvas 方式有何区别?[^3] 3. 在哪些场景下需要手动关闭 GPU 硬件加速? 4. 如何实现 Bitmap 的实时滤镜处理并调用 GPU 加速?[^4] [^1]: Android 默认 GPU 绘制机制 [^2]: Display List 转换 XML 布局为 GPU 指令 [^3]: 调用 GPU 算力优化计算密集型任务 [^4]: GPU 加速实现图像处理(如高斯模糊)
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值