前言

在Android设备内存动不动就上G的情况下,的确没有必要去太在意APP对Android系统内存的消耗,但在实际工作中我做的是教育类的小学APP,APP中的按钮、背景、动画变换基本上全是图片,在2K屏上(分辨率2048*1536)一张背景图片就会占用内存12M,来回切换几次内存占用就会增涨到上百兆,为了在不影响APP的视觉效果的前提下,有必要通过各种手段来降低APP对内存的消耗。

通过DDMS的APP内存占用查看工具分析发现,APP中占用内存最多的是图片,每个Activity中图片占用内存占大半,本文重点分享对图片的内存优化。

不要将Button的背景设置为selector

在布局文件和代码中,都可以为Button设置background为selector,这样方便实现按钮的正反选效果,但实际跟踪发现,如果是将Button的背景设置为selector,在初始化Button的时候会将正反选图片都加载在内存中(具体可以查看Android源码,在类Drawable.java的createFromXmlInner方法中对图片进行解析,最终调用Drawable的inflate方法),相当于一个按钮占用了两张相同大小图片所使用的内存,如果一个界面上按钮很多或者是按钮很大,光是按钮占用的内存就会很大,可以通过在布局文件中给按钮只设置正常状态下的背景图片,然后在代码中监听按钮的点击状态,当按下按钮时为按钮设置反选效果的图片,抬起时重新设置为正常状态下的背景,具体实现方式如下:

public class ImageButtonClickUtils {

private ImageButtonClickUtils(){

}

/**

* 设置按钮的正反选效果

*

* */

public static void setClickState(View view, final int normalResId, final int pressResId){

view.setOnTouchListener(new OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

switch(event.getAction()){

case MotionEvent.ACTION_DOWN:{

v.setBackgroundResource(pressResId);

}

break;

case MotionEvent.ACTION_MOVE:{

v.setBackgroundResource(pressResId);

}

break;

case MotionEvent.ACTION_UP:{

v.setBackgroundResource(normalResId);

}

break;

default:{

}

break;

}

// 为了不影响监听按钮的onClick回调,返回值应为false

return false;

}

});

}

}

通过上面这种方式就可以解决同一个按钮占用两倍内存的问题,如果你觉得为一个按钮提供正反选两张图片会导致APK的体积变大,可以通过如下方式实现按钮点击的反选效果,这种方式既不会存在Button占用两倍内存的情况,又减小了APK的体积(Android 5.0中的tintColor也可以实现类似的效果):

ImageButton personalInfoBtn = (ImageButton)findViewById(R.id.personalBtnId);

personalInfoBtn.setOnTouchListener(new OnTouchListener() {

@SuppressLint("ClickableViewAccessibility")

@Override

public boolean onTouch(View v, MotionEvent event) {

int action = event.getAction();

if(action == MotionEvent.ACTION_DOWN){

((ImageButton)v).setColorFilter(getResources().getColor(0X50000000));

}else if(action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL){

((ImageButton)v).clearColorFilter();

}

// 为了不影响监听按钮的onClick回调,返回值应为false

return false;

}

});

将背景图片放在非UI线程绘制,提升APP的效率

在高分辨率的平板设备上,绘制大背景的图片会影响程序的运行效率,严重情况下就和没有开硬件加速的时候使用手写功能一样,相当地卡,最后我们的解决方案是将背景图片通过SurfaceView来绘制,这样相当于是在非UI线程绘制,不会影响到UI线程做其它事情:

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.Canvas;

import android.graphics.Matrix;

import android.graphics.PixelFormat;

import android.util.AttributeSet;

import android.util.DisplayMetrics;

import android.view.SurfaceHolder;

import android.view.SurfaceView;

import com.eebbk.hanziLearning.activity.R;

public class RootSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable{

private float mViewWidth = 0;

private float mViewHeight = 0;

private int mResourceId = 0;

private Context mContext = null;

private volatile boolean isRunning = false;

private SurfaceHolder mSurfaceHolder = null;

public RootSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

initRootSurfaceView(context, attrs, defStyleAttr, 0);

}

public RootSurfaceView(Context context, AttributeSet attrs) {

super(context, attrs);

initRootSurfaceView(context, attrs, 0, 0);

}

private void initRootSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes){

mContext = context;

DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RootSurfaceView, defStyleAttr, defStyleRes);

int n = a.getIndexCount();

mViewWidth = displayMetrics.widthPixels;

mViewHeight = displayMetrics.heightPixels;

for(int index=0; index

int attr = a.getIndex(index);

switch(attr){

case R.styleable.RootSurfaceView_background:{

mResourceId = a.getResourceId(attr, 0);

}

break;

case R.styleable.RootSurfaceView_view_width:{

mViewWidth = a.getDimension(attr, displayMetrics.widthPixels);

}

break;

case R.styleable.RootSurfaceView_view_height:{

mViewHeight = a.getDimension(attr, displayMetrics.heightPixels);

}

break;

default:{

}

break;

}

}

a.recycle();

mSurfaceHolder = getHolder();

mSurfaceHolder.addCallback(this);

mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);

}

private Bitmap getDrawBitmap(Context context, float width, float height) {

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mResourceId);

Bitmap resultBitmap = zoomImage(bitmap, width, height);

return resultBitmap;

}

@Override

public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {

System.out.println("RootSurfaceView surfaceChanged");

}

@Override

public void surfaceCreated(SurfaceHolder holder) {

drawBackGround(holder);

System.out.println("RootSurfaceView surfaceCreated");

}

@Override

public void surfaceDestroyed(SurfaceHolder holder) {

isRunning = false;

System.out.println("RootSurfaceView surfaceDestroyed");

}

@Override

protected void onAttachedToWindow() {

super.onAttachedToWindow();

System.out.println("RootSurfaceView onAttachedToWindow");

}

@Override

protected void onDetachedFromWindow() {

super.onDetachedFromWindow();

System.out.println("RootSurfaceView onDetachedFromWindow");

}

@Override

public void run(){

while(isRunning){

synchronized (mSurfaceHolder) {

if(!mSurfaceHolder.getSurface().isValid()){

continue;

}

drawBackGround(mSurfaceHolder);

}

isRunning = false;

break;

}

}

private void drawBackGround(SurfaceHolder holder) {

Canvas canvas = holder.lockCanvas();

Bitmap bitmap = getDrawBitmap(mContext, mViewWidth, mViewHeight);

canvas.drawBitmap(bitmap, 0, 0, null);

bitmap.recycle();

holder.unlockCanvasAndPost(canvas);

}

public static Bitmap zoomImage( Bitmap bgimage , float newWidth , float newHeight ) {

float width = bgimage.getWidth( );

float height = bgimage.getHeight( );

Matrix matrix = new Matrix();

float scaleWidth = newWidth/width;

float scaleHeight = newHeight/height;

matrix.postScale( scaleWidth, scaleHeight );

Bitmap bitmap = Bitmap.createBitmap( bgimage, 0, 0, ( int ) width , ( int ) height, matrix, true );

if( bitmap != bgimage ){

bgimage.recycle();

bgimage = null;

}

return bitmap;

}

}

在res/values/attr.xml文件中定义自定义View的自定义属性:

没有必要使用硬件加速的界面建议关掉硬件加速

通过DDMS的heap跟踪发现,相比于关闭硬件加速,在打开硬件加速的情况下会消耗更多的内存,但有的界面打开或者关闭硬件加速对程序的运行效率并没有太大的影响,此种情况下可以考虑在AndroidManifest.xml文件中关闭掉对应Activity的硬件加速,like this:

android:name=".SettingActivity"

android:hardwareAccelerated="false"

android:screenOrientation="sensorLandscape"

android:theme="@style/Translucent_NoTitle">

注意:如果使用到WebView、视频播放、手写、动画等功能时,关掉硬件加速会严重音效程序的运行效率,这种情况可以只关闭掉Activity中某些view的硬件加速,整个Activity的硬件加速不关闭。

如果Activity中某个View需要关闭硬件加速,但整个Activity不能关闭,可以调用view层级关闭硬件加速的方法:

// view.setLayerType || 在定义view的构造方法中调用该方法

setLayerType(View.LAYER_TYPE_SOFTWARE, null);

尽量少用AnimationDrawable,如果必须要可以自定义图片切换器代替AnimationDrawable

AnimationDrawable也是一个耗内存大户,图片帧数越多耗内存越大,具体可以查看AnimationDrawable的源码,在AnimationDrawable实例化的时候,Drawable的createFromXmlInner方法会调用AnimationDrawable的inflate方法,该方法里面有一个while循环去一次性将所有帧都读取出来,也就是在初始化的时候就将所有的帧读在内存中了,有多少张图片,它就要消耗对应大小的内存。

虽然可以通过如下方式释放AnimationDrawable占用的内存,但是当退出使用AnimationDrawable的界面,再次进入使用其播放动画时,会报使用已经回收了的图片的异常,这个应该是Android对图片的处理机制导致的,虽然Activity被finish掉了,但是这个Activity中使用到的图片还是在内存中,如果被回收,下次进入时就会报异常信息:

/**

* 释放AnimationDrawable占用的内存

*

*

* */

@SuppressWarnings("unused")

private void freeAnimationDrawable(AnimationDrawable animationDrawable) {

animationDrawable.stop();

for (int i = 0; i < animationDrawable.getNumberOfFrames(); ++i){

Drawable frame = animationDrawable.getFrame(i);

if (frame instanceof BitmapDrawable) {

((BitmapDrawable)frame).getBitmap().recycle();

}

frame.setCallback(null);

}

animationDrawable.setCallback(null);

}

通常情况下我会自定义一个ImageView来实现AnimationDrawable的功能,根据图片之间切换的时间间隔来定时设置ImageView的背景图片,这样始终只是一个ImageView实例,更换的只是其背景,占用内存会比AnimationDrawable小很多:

/**

* 图片动态切换器

*

* */

public class AnimImageView {

private static final int MSG_START = 0xf1;

private static final int MSG_STOP = 0xf2;

private static final int STATE_STOP = 0xf3;

private static final int STATE_RUNNING = 0xf4;

/* 运行状态*/

private int mState = STATE_RUNNING;

private ImageView mImageView;

/* 图片资源ID列表*/

private List mResourceIdList = null;

/* 定时任务*/

private Timer mTimer = null;

private AnimTimerTask mTimeTask = null;

/* 记录播放位置*/

private int mFrameIndex = 0;

/* 播放形式*/

private boolean isLooping = false;

public AnimImageView( ){

mTimer = new Timer();

}

/**

* 设置动画播放资源

*

* */

public void setAnimation( HanziImageView imageview, List resourceIdList ){

mImageView = imageview;

mResourceIdList = resourceIdList;

}

/**

* 开始播放动画

* @param loop 时候循环播放

* @param duration 动画播放时间间隔

* */

public void start(boolean loop, int duration){

stop();

isLooping = loop;

mFrameIndex = 0;

mState = STATE_RUNNING;

mTimeTask = new AnimTimerTask( );

mTimer.schedule(mTimeTask, 0, duration);

}

/**

* 停止动画播放

*

* */

public void stop(){

if (mTimeTask != null) {

mFrameIndex = 0;

mState = STATE_STOP;

mTimer.purge();

mTimeTask.cancel();

mTimeTask = null;

mImageView.setBackgroundResource(0);

}

}

/**

* 定时器任务

*

*

*/

class AnimTimerTask extends TimerTask {

@Override

public void run() {

if(mFrameIndex < 0 || mState == STATE_STOP){

return;

}

if( mFrameIndex < mResourceIdList.size() ){

Message msg = AnimHanlder.obtainMessage(MSG_START,0,0,null);

msg.sendToTarget();

}else{

mFrameIndex = 0;

if(!isLooping){

Message msg = AnimHanlder.obtainMessage(MSG_STOP,0,0,null);

msg.sendToTarget();

}

}

}

}

private Handler AnimHanlder = new Handler(){

public void handleMessage(android.os.Message msg) {

switch (msg.what) {

case MSG_START:{

if(mFrameIndex >=0 && mFrameIndex < mResourceIdList.size() && mState == STATE_RUNNING){

mImageView.setImageResource(mResourceIdList.get(mFrameIndex));

mFrameIndex++;

}

}

break;

case MSG_STOP:{

if (mTimeTask != null) {

mFrameIndex = 0;

mTimer.purge();

mTimeTask.cancel();

mState = STATE_STOP;

mTimeTask = null;

mImageView.setImageResource(0);

}

}

break;

default:

break;

}

}

};

}

其它优化方式

1、尽量将Activity中的小图片和背景合并,一张小图片既浪费布局的时间,又平白地增加了内存占用;

2、不要在Activity的主题中为Activity设置默认的背景图片,这样会导致Activity占用的内存翻倍:

android 多图片优化工具,总结Android App内存优化之图片优化相关推荐

  1. Android 开源项目android-open-project工具库解析之(一) 依赖注入,图片缓存,网络相关,数据库orm工具包,Android公共库...

    一.依赖注入DI 通过依赖注入降低View.服务.资源简化初始化.事件绑定等反复繁琐工作 AndroidAnnotations(Code Diet) android高速开发框架 项目地址:https: ...

  2. android apk 反编译 工具下载,android APK反编译工具Apktool

    这是android APK反编译工具Apktool下载,集成 jd-gui.jad.dex2jar.apktool.使用该工具可以反编译apk文件,查看xml以及java源代码,默认使用jad反编译, ...

  3. android 上传头像工具类,android 圆角头像工具类

    看看相关文章, [支持圆形.圆角矩形.带边框的自定View]:https://github.com/msandroid/MultiShapeView import android.content.Co ...

  4. AI PNG Enlarger (AI图片放大工具) - 用深度学习AI算法放大图片,不模糊并保持透明度

    大家通常放大一个图片,无论是将尺寸放大还是在浏览过程中用放大镜工具时,都会发现放大了的图片会变模糊甚至出现马赛克状.有没有办法将图片放大还保持清晰度呢? AI PNG Enlarger是一款免费的Wi ...

  5. android php实时聊天工具,Android_Android 应用APP加入聊天功能,简介 自去年 LeanCloud 发布实时 - phpStudy...

    Android 应用APP加入聊天功能 简介 自去年 LeanCloud 发布实时通信(IM)服务之后,基于用户反馈和工程师对需求的消化和对业务的提炼,上周正式发布了「实时通信 2.0 」.设计理念依 ...

  6. 网络请求以及网络请求下载图片的工具类 android开发java工具类

    2019独角兽企业重金招聘Python工程师标准>>> package cc.jiusan.www.utils;import org.apache.http.HttpEntity; ...

  7. android ps切图工具下载,移动APP设计之PS切图插件大汇总,值得收藏

    移动APP界面设计切图和标注是一项必不可少的步骤.所以,设计界也涌现出了很多切图神器和标注神器. 包括25学堂之前介绍过的很多切图工具和切图教程. 今天25学堂的小编为大家精选了几个不错的移动APP设 ...

  8. android 图片缓存工具类,Android工具类系列-Glide图片缓存与圆角

    Glide的图片缓存和清除图片缓存 public class GlideCacheUtil { private static GlideCacheUtil inst; public static Gl ...

  9. android 上传头像工具类,Android开发中如何实现头像的更换与上传

    AlertDialog.Builder builder = new AlertDialog.Builder(Main2Activity.this); builder.setTitle("添加 ...

最新文章

  1. 实践自定义UI—View
  2. centos解压zip命令_2、centos下安装elasticsearch-head
  3. java.lang.ExceptionInInitializerError
  4. ios中base64编码
  5. C++双冒号和单冒号的用法区别
  6. JavaScript网络地址作为参数_JavaScript之bind的模拟实现
  7. 总结构建子类对象时的顺序
  8. 俞敏洪辟谣“周末暑假不能上课”:人生已经不易 为何还要捅刀
  9. 对中职计算机教学的思考,中职计算机教学思考
  10. Exchange2007使用POP3/SMTP协议收发邮件
  11. 刘润老师的5分钟商学院营销案例~比例偏见!
  12. 英语音标音节与自然拼读总结
  13. 剑指offer 09、30:栈与队列
  14. 深度解析反思型Essay怎么写?
  15. Hadoop学习(二)---Secondary结点的配置以及HDFS的常用命令以及API的使用
  16. A1088 Rational Arithmetic (20 分)
  17. adb运行以及adb常用命令
  18. React native大版本迭代信息记录
  19. matlab 矩阵的n次,用matlab的for循环产生N个矩阵,怎么取第N次的矩阵?
  20. 风控模型中的KS指标

热门文章

  1. q函数表格怎么看_会计表格函数玩不会?送你会计表格函数公式大全,财务人都在用...
  2. Redis 解决了哪些问题?
  3. centos安装mysql wsl_wsl安装/卸载mysql
  4. cyyz: Day 4 网络流整理
  5. [PWA] Check Online Status by using the NavigatorOnLine API
  6. iOS swift语言生成条形码,可一次性生成多个!并带文字
  7. NSURLSession实现文件上传
  8. Android----View
  9. 云计算乱局:你真的懂,什么叫做云吗?(一)
  10. 【信息系统项目管理师】第5章-项目范围管理 知识点详细整理