首页 > 代码库 > Android button 性能探讨
Android button 性能探讨
button是Android 中经常使用的控件。 父类是 Textview, 在之前的文章中对 Textview的一些 基本信息都介绍过, 还包含Textview的 之 setText方法 带来的一些 性能问题。 这篇文章就讲到了 button的一些使用要注意的地方。
这些须要注意的 还包含了 Textview 已经一些 Textview的 子类 如 CheckBox,EditText 等等 一些控件。
问题的产生
button 我们一般都会给它 设置一个background, 很多人会做成 selector 或者 shape 。layer-list等等一些 资源。
有的做的美丽的项目 会给 button 两张图片 。一张 是选中 或者点击状态,另外一张 则是 正常状态。
<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载入的时候 这两张图片也载入了。 也就是一个button 就占用了 两张 图片的内存,这是不合理的。
那有人说 我都这么用 没一点事, 确实 随着如今 机器硬件的提升。以及Android本身系统的优化 等 大幅提高了效率, 使得这样的 问题 变的不会非常明显, 你一个xml 可能 使用带有图片的 selector 可能也就那么几个 不会非常多。 那么我们讲的是为什么 会出现这个问题 ,怎么避免。 至于一些 临界点(使用多少会出现性能问题 造成卡顿)暂不关心。
那么 为什么会载入两张,由于我们在 xml中配置,Drawable.java的createFromXmlInner方法中对图片进行解析,终于调用Drawable的inflate方法),相当于一个button占用了两张同样大小图片所使用的内存。 我们来看看 源代码
<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 就尽量使用。 对内存和性能 比图片更好一点
Android button 性能探讨