Android button 性能探讨

本文探讨了在Android中使用Button时可能会遇到的性能问题,特别是当为Button设置背景图片时,可能导致内存占用增加。通过分析源码,指出即使在XML中使用selector或shape资源,也会加载所有状态的图片。建议在可能的情况下使用颜色资源代替图片,或者动态设置图片以节省内存。同时,提倡使用自定义stroke、layer-list和shape来替代图片,以提高性能和减少APK大小。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 button是Android 中常用的控件, 父类是 Textview, 在之前的文章中对 Textview的一些 基本信息都介绍过, 还包括Textview的 之 setText方法 带来的一些 性能问题。   这篇文章就讲到了 button的一些使用要注意的地方。

 这些需要注意的  还包括了 Textview 已经一些 Textview的 子类 如 CheckBox,EditText 等等 一些控件。 问题的产生

   button  我们一般都会给它 设置一个background, 许多人会做成 selector 或者 shape ,layer-list等等一些 资源。

有的做的漂亮的项目 会给 按钮 两张图片 ,一张 是选中 或者点击状态,另外一张 则是 正常状态。   

<span style="font-size:18px;"><?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
      <!-- checked -->
    <item android:drawable="@drawable/language_press" android:state_checked="true"/>
    <!-- default -->
    <item android:drawable="@drawable/language_no_press" android:state_checked="false"/>
</selector></span>


那么这样使用 就使得 xml加载的时候  这两张图片也加载了, 也就是一个按钮 就占用了 两张 图片的内存,这是不合理的。 那有人说 我都这么用 没一点事,  确实  随着现在 机器硬件的提升,以及Android本身系统的优化 等 大幅提高了效率, 使得这种 问题 变的不会很明显,  你一个xml 可能  使用带有图片的 selector  可能也就那么几个 不会很多。 那么我们讲的是为什么 会出现这个问题 ,怎么避免。 至于一些 临界点(使用多少会出现性能问题 造成卡顿)暂不关心。

那么 为什么会加载两张,因为我们在 xml中配置,Drawable.java的createFromXmlInner方法中对图片进行解析,最终调用Drawable的inflate方法),相当于一个按钮占用了两张相同大小图片所使用的内存。 我们来看看 源码

<span style="font-size:18px;"> public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs)
    throws XmlPullParserException, IOException {
        Drawable drawable;

        final String name = parser.getName();

        if (name.equals("selector")) {
            drawable = new StateListDrawable();
        } else if (name.equals("level-list")) {
            drawable = new LevelListDrawable();
        /* Probably not doing this.
        } else if (name.equals("mipmap")) {
            drawable = new MipmapDrawable();
        */
        } else if (name.equals("layer-list")) {
            drawable = new LayerDrawable();
        } else if (name.equals("transition")) {
            drawable = new TransitionDrawable();
        } else if (name.equals("color")) {
            drawable = new ColorDrawable();
        } else if (name.equals("shape")) {
            drawable = new GradientDrawable();
        } else if (name.equals("scale")) {
            drawable = new ScaleDrawable();
        } else if (name.equals("clip")) {
            drawable = new ClipDrawable();
        } else if (name.equals("rotate")) {
            drawable = new RotateDrawable();
        } else if (name.equals("animated-rotate")) {
            drawable = new AnimatedRotateDrawable();            
        } else if (name.equals("animation-list")) {
            drawable = new AnimationDrawable();
        } else if (name.equals("inset")) {
            drawable = new InsetDrawable();
        } else if (name.equals("bitmap")) {
            drawable = new BitmapDrawable(r);
            if (r != null) {
               ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
            }
        } else if (name.equals("nine-patch")) {
            drawable = new NinePatchDrawable();
            if (r != null) {
                ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
             }
        } else {
            throw new XmlPullParserException(parser.getPositionDescription() +
                    ": invalid drawable tag " + name);
        }

        drawable.inflate(r, parser, attrs);
        return drawable;
    }</span>


可以看到 源码中 做了 很多判断,根据xml中的配置 加载了 不同的资源(我的源码可能是老版本的,新版本的 我看了 做了优化 匹配都变为了 switch/case模式)。可以看到 使用图片的话 会用BitmapDrawable, 使用BitmapDrawable 这个类里面的inflate的方法。 会将图片加载到内存中。并绘制出来 

 如果在内存吃紧的情况下 做一些 小优化还是非常有必要的。

 比如 在 图片是 纯颜色的 时候 建议selector 里面使用 颜色  而不使用 图片。具体原因 还是源码 

 下面是BitmapDrawable的inflate方法

<span style="font-size:18px;"> @Override
    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
            throws XmlPullParserException, IOException {
        super.inflate(r, parser, attrs);

        TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable);

        final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0);
        if (id == 0) {
            throw new XmlPullParserException(parser.getPositionDescription() +
                    ": <bitmap> requires a valid src attribute");
        }
        final Bitmap bitmap = BitmapFactory.decodeResource(r, id);
        if (bitmap == null) {
            throw new XmlPullParserException(parser.getPositionDescription() +
                    ": <bitmap> requires a valid src attribute");
        }
        mBitmapState.mBitmap = bitmap;
        setBitmap(bitmap);
        setTargetDensity(r.getDisplayMetrics());

        final Paint paint = mBitmapState.mPaint;
        paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias,
                paint.isAntiAlias()));
        paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter,
                paint.isFilterBitmap()));
        paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither,
                paint.isDither()));
        setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL));
        int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1);
        if (tileMode != -1) {
            switch (tileMode) {
                case 0:
                    setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
                    break;
                case 1:
                    setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
                    break;
                case 2:
                    setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
                    break;
            }
        }

        a.recycle();
    }</span>


下面是 ColorDrawable的inflate的源码

 

<span style="font-size:18px;"> @Override
    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
            throws XmlPullParserException, IOException {
        super.inflate(r, parser, attrs);

        TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.ColorDrawable);

        int color = mState.mBaseColor;
        color = a.getColor(com.android.internal.R.styleable.ColorDrawable_color, color);
        mState.mBaseColor = mState.mUseColor = color;

        a.recycle();
    }</span>

 具体一对比,不说细节,光说代码量就能说明一点问题了吧。 如果非要使用 图片,就用代码 进行动态设置。

 给要使用的view加 onTouch事件,在 down,和move的时候为 点击状态, up为正常状态。觉得每一个设置太麻烦?

 那就封装一个方法

<span style="font-size:18px;">    public void changeImage(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;
                 }
             // 为了不影响其他事件
              return false;
            }
        });
    }</span>


这样不仅 节省了内存, 而且 能减小apk包的大小(不需要每个都写 selector.xml了)

 有些 边框,线条,圆角等等  能自己写 stroke,layer-list,shape 就尽量使用。 对内存和性能 比图片更好一点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

a3280028

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值