学习网上面以为厉害的博主的文章,自己跟着学习的节奏,也一步一步的进行学着分析,加深印象,更重要的是自己能够熟悉里面的细节部分,细节决定一些性能.
首先写了一个测试demo程序如下:
<1> 新建Android工程,工程树如下:
<2> : 程序如下:
DurianMainActivity.java
package com.durian.durianperformanceview; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import com.durian.view.DurianView; public class DurianMainActivity extends Activity { private Button mButton; private DurianView mDurianView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.durian); mDurianView=(DurianView)findViewById(R.id.durianview); mButton=(Button)findViewById(R.id.button); mButton.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { // TODO Auto-generated method stub mDurianView.updateDraw(); } }); } }
DurianView.java
/** * @Title: DurianView.java * @Package com.durian.view * @Description: TODO * @author zhibao.liu from durian organization * @date 2015-12-28 下午06:25:33 * @version V1.0 */ package com.durian.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import com.durian.durianperformanceview.R; import com.durian.utils.DurianUtils; /** * @ClassName: DurianView * @Description: TODO * @author zhibao.liu Freelancer * @email [email protected] * @date 2015-12-28 下午06:25:33 * */ public class DurianView extends View { private final static String TAG = "DurianView"; private TypedArray mTypeArray; private String mDurianText; private float mDurianTextWidth; private float mDurianTextSize; private int mDurianLeft; private int mDurianRight; private int mDurianPosition; private Paint mPaint; private Bitmap mBitmap; private Shader mRadialGradient = null; private Paint mRadialPaint; public DurianView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub } public DurianView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub // initView(context,attrs); } public DurianView(Context context) { super(context); // TODO Auto-generated constructor stub } private void initView(Context context,AttributeSet attrs){ mTypeArray = context.obtainStyledAttributes(attrs, R.styleable.durian_view); mDurianText = mTypeArray.getString(R.styleable.durian_view_durian_text); mDurianTextSize = mTypeArray.getInteger( R.styleable.durian_view_durian_textsize, 16); mDurianLeft = mTypeArray.getInteger( R.styleable.durian_view_durian_leftpadding, 0); mDurianRight = mTypeArray.getInteger( R.styleable.durian_view_durian_rightpadding, 0); mDurianPosition = mTypeArray.getInteger( R.styleable.durian_view_durian_labelpos, 0); mPaint = new Paint(); mPaint.setColor(Color.RED); mPaint.setTextSize(DurianUtils.px2sp(context, mDurianTextSize)); mDurianTextWidth = mPaint.measureText(mDurianText); Drawable map = mTypeArray .getDrawable(R.styleable.durian_view_durian_src); mBitmap = DurianUtils.drawableToBitmap(map); // 创建RadialGradient对象 // 第一个,第二个参数表示渐变圆中心坐标 // 第三个参数表示半径 // 第四个,第五个,第六个与线性渲染相同 mRadialGradient = new RadialGradient(250, 250, 100, new int[] { Color.GREEN, Color.RED, Color.BLUE, Color.WHITE }, null, Shader.TileMode.REPEAT); mRadialPaint = new Paint(); mRadialPaint.setShader(mRadialGradient); } public void updateDraw() { postInvalidate(); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); // canvas.clipRect(new Rect(0, 0, (int) (50 + mDurianTextWidth), 50 + 5)); // canvas.drawText(mDurianText, 50, 50, mPaint); // canvas.drawLine(10.0f, 50.0f, 100.0f, 50.0f, mPaint); // canvas.drawBitmap(mBitmap, 0, 55, mPaint); // canvas.drawCircle(250f, 250f, 100f, mPaint); // canvas.drawCircle(250f, 250f, 100f, mRadialPaint); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // TODO Auto-generated method stub super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); int w = resolveSizeAndState(minw, widthMeasureSpec, 1); int minh = MeasureSpec.getSize(w) - (int) mDurianTextWidth + getPaddingBottom() + getPaddingTop(); int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int) mDurianTextWidth, heightMeasureSpec, 0); setMeasuredDimension(w, h); } }
下面是一个工具类:
DurianUtils.java
/** * @Title: DurianUtils.java * @Package com.durian.utils * @Description: TODO * @author zhibao.liu from durian organization * @date 2015-12-29 上午10:07:53 * @version V1.0 */ package com.durian.utils; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import com.durian.durianperformanceview.R; /** * @ClassName: DurianUtils * @Description: TODO * @author zhibao.liu Freelancer * @email [email protected] * @date 2015-12-29 上午10:07:53 * */ public class DurianUtils { public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } public static int dip2px(Context context, float dipValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } public static int px2sp(Context context, float pxValue) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (pxValue / fontScale + 0.5f); } public static int sp2px(Context context, float spValue) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (spValue * fontScale + 0.5f); } public static Bitmap drawableToBitmap(Drawable drawable) { Bitmap bitmap = Bitmap.createBitmap( drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); Canvas canvas = new Canvas(bitmap); // canvas.setBitmap(bitmap); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.draw(canvas); return bitmap; } public static Bitmap getResBitmap(Context context) { Resources res = context.getResources(); Bitmap bmp = BitmapFactory.decodeResource(res, R.drawable.debug); return bmp; } }
布局文件:durian.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:durian_view="http://schemas.android.com/apk/res/com.durian.durianperformanceview" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.durian.view.DurianView android:layout_width="fill_parent" android:layout_height="60dp" android:id="@+id/durianview" durian_view:durian_text="zhibao.liu" durian_view:durian_textsize="64" durian_view:durian_src="@drawable/debug" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="button" android:id="@+id/button"/> </LinearLayout>
属性文件:attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="durian_view"> <attr name="durian_text" format="string"/> <attr name="durian_textsize" format="integer"/> <attr name="durian_leftpadding" format="integer"/> <attr name="durian_rightpadding" format="integer"/> <attr name="durian_src" format="reference"/> <attr name="durian_labelpos" format="enum"> <enum name="left" value="0"></enum> <enum name="right" value="1"></enum> </attr> </declare-styleable> </resources>
<3> : 下面是个人通过修改上面,得出的一些结论,仅仅共参考用.
a> : 先修改durian.java中DurianView这个自定View的布局大小:
<com.durian.view.DurianView android:layout_width="fill_parent" android:layout_height="60dp" android:id="@+id/durianview" durian_view:durian_text="zhibao.liu" durian_view:durian_textsize="64" durian_view:durian_src="@drawable/debug" />
启动程序后,打开DDMS窗口:
选中测试的工程,如上.这里面暂时看耗时, 只需要学会下面的参考.
运行APP有一个按钮,点击APP界面按钮之前,先点击上面右边带红点的图标,如下:
然后点击APP界面的按钮,然后在点击上面右边的图标,图标由灰色转变带红色点了,同时:
上面的eclipse会自动弹出来的.主要看第一行,即:
第一次看上面一堆的东西,什么Incl Cpu Time等参数可能不知道说什么,我参看了下面的 一片文章非常的好:
http://www.oschina.net/news/56500/traceview-android
上面的文章非常的好,对于一般的分析,主要参看下面的就好了:
在耗时上面,看Incl Real Time 参数,Calls+RecurC...参数,Cpu Time/Call参数,还有Real Time/Call参数
Incl Real Time 参数 : 程序运行需要的事件;
Calls+RecruC...参数 : 这段程序/或者程序中某个具体的方法被运行的次数;
Cpu Time/Call参数 : 这段程序/程序中某个具体的方法被运行所需要的时间,特指CPU中;
Real Time/Call参数: 这段程序/程序中某个具体的方法被运行所需要的时间,一般具体程序运行的时间,一般这个时间大于Cpu Time/Call参数的时间.
所以上面总耗时185.765ms
例如:177
这个updateDraw方法是我们点击APP里面的按钮被调用刷新View的:
从面的参数可以看出,点击一次按钮后,执行一次,所需时间0.305s,CPU时间和实际执行程序时间差不多相等.
但是上面发送更新后,执行onDraw方法需要的时间是多少呢,这里可以看一下294:
虽然上面的程序还没有添加任何东西,是直接放空的,耗时如下:
从上面参数可以看出onDraw被执行一次,所需耗时0.122.
可以这样想一下,如果这个onDraw方法好了大量的时间,APP在显示的时候是什么样的呢?
下面我们修改自定义View的暂用尺寸大小:
<com.durian.view.DurianView android:layout_width="fill_parent" android:layout_height="600dp" android:id="@+id/durianview" durian_view:durian_text="zhibao.liu" durian_view:durian_textsize="64" durian_view:durian_src="@drawable/debug" />
高度从60dp调整到600dp ,执行程序,操作刷新一次:
总耗时变成219.674,注意前面只有100多.
但是看onDraw方法是看不出的,因为太小了,所以下面的在onDraw操作量不大的时候,数字上无法做出判断.
结论一 : 从上面看,布局空间大小的设置对APP刷新所需的时间是有影响的.
b> : 自定View初始化变量:
public DurianView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub initView(context,attrs); }
将程序中的initView注释去掉,同样运行APP,会有以下结论.
结论二 : 构造中初始化过多,对View的创建过程会有影响,但是也有一个好处,对后面的刷新,因为不需要创建对象,所以不会增加耗时和内存,很多时候开发会在onDraw方法中再次创建一些对象,会增加内存泄露,但是由于每次都创建,耗时不会增加.但是内存泄露也是需要避免的.
c> : onDraw方法中修改如下:
@Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); // canvas.clipRect(new Rect(0, 0, (int) (50 + mDurianTextWidth), 50 + 5)); canvas.drawText(mDurianText, 50, 50, mPaint); canvas.drawLine(10.0f, 50.0f, 100.0f, 50.0f, mPaint); // canvas.drawBitmap(mBitmap, 0, 55, mPaint); // canvas.drawCircle(250f, 250f, 100f, mPaint); // canvas.drawCircle(250f, 250f, 100f, mRadialPaint); }
运行总耗时: 245.313
去掉下面注释:
canvas.clipRect(new Rect(0, 0, (int) (50 + mDurianTextWidth), 50 + 5));
重新运行:
运行总耗时: 164.254
从下面可以得出结论:
结论三 : 在画布上面再划出一个区域来绘制,刷新会更快.
d> : 画一个圆,用不同的东西对圆进行填充.
一种是填充红色:
canvas.drawCircle(250f, 250f, 100f, mPaint);
一种是渲染式填充:
canvas.drawCircle(250f, 250f, 100f, mRadialPaint);
结论四 : 这个可能图像在重画时,不同颜色填充耗时有些差别---这个不一定啊,但是APP颜色对耗电不同,这是确定的
|-----------------------------------------------------------------------------------------------------------------------------------------------------|
下面是一些基本的总结,仅仅共参考意见:
View的一个标准 : 为了避免UI显得卡顿,你必须确保动画能够保持在60fps以上.
这个东西以前的公司设定的下线就是60fps,但现在看来原来差不多是一个标准了,用来衡量View显示是否存在卡顿.
<1> : View的尺寸大小,以及自身绘制区域的大小需要严格控制.
精确定位View的大小,可以在View中自己实现:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Try for a width based on our minimum int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); int w = resolveSizeAndState(minw, widthMeasureSpec, 1); // Whatever the width ends up being, ask for a height that would let the pie // get as big as it can int minh = MeasureSpec.getSize(w) - (int)mTextWidth + getPaddingBottom() + getPaddingTop(); int h = resolveSizeAndState(MeasureSpec.getSize(w) - (int)mTextWidth, heightMeasureSpec, 0); setMeasuredDimension(w, h); }
最终通过setMeasuredDimension(w,h);设置View的大小.
<2> : 当View需要一些动画效果时,能够使用Android ***Animator产生动画效果的,尽量不适用onDraw重新绘制,因为***Animator调用时不会执行onDraw重新绘制,推荐ValueAnimator,ObjectAnimator,更复杂一点的ViewPropertyAnimator组合属性动画.
如果实在迫不得已,一般相应touch事件,Scroller设置产生的移动动画,但是从View事件传递的手稿中,我们知道start这个动画效果,是需要强制执行重绘的,所以这种相对上面的效果可能低一些.
<3> : View嵌套布局水平布局和纵向布局:
水平方向布局:
纵向方向布局:
由于经常刷新,会导致View经常重绘,如果是纵向方向布局,就会导致一个View的子View也会出现重绘的现象,从而导致产生一连锁反应,本想只绘制一个View,结果所有的子View也重新绘制了.
所以一般情况,水平(平级)布局要优于纵向布局,有必要都需要使用ViewGroup容器去搞定View,不要在View中反复嵌套子View.
同时有必要提一下,反复的include包含布局其实也很恶心,没有必要的话,建议不要经常用include,天下没有白送的午餐,不要图一时爽.
另外一个非常耗时的操作是请求layout。任何时候执行requestLayout(),会使得Android UI系统去遍历整个View的层级来计算出每一个view的大小。如果找到有冲突的值,它会需要重新计算好几次。另外需要尽量保持View的层级是扁平化的,这样对提高效率很有帮助。 如果你有一个复杂的UI,你应该考虑写一个自定义的ViewGroup来执行他的layout操作。与内置的view不同,自定义的view可以使得程序仅仅测量这一部分,这避免了遍历整个view的层级结构来计算大小
<4> : 在一个View在屏幕翻转的时候,尺寸调整须知,屏幕在翻转时,调用:
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // TODO Auto-generated method stub super.onSizeChanged(w, h, oldw, oldh); }
onSizeChanged(),当你的view第一次被赋予一个大小时,或者你的view大小被更改时会被执行。在onSizeChanged方法里面计算位置,间距等其他与你的view大小值
<5> : 在不得不调用重绘通知时,即调用invalidate方法时,也需要注意,这个方法3种,参见的是invalidate()不带任何参数的,按照上面的,画布大小对刷新也是有影响的,那么专家(不是我)建议使用带四个参数的invalidate(l, t, r, b)方法,对指定的区域进行刷新就好了,如果使用不带参数的invalidate(),那默认就是整个区域都进行刷新---这是强制性的.
<6> : 硬件加速 :
硬件加速虽然有很多好处,如在翻转,平移等动画方面不错,但是并不是所有的都很好,甚至效果不理想.
在View中,在你需要的时候添加:
if(!isInEditMode()){ setLayerType(View.LAYER_TYPE_HARDWARE, null); }
但不需要的时候:
setLayerType(View.LAYER_TYPE_NONE, null);
硬件加速可能遭遇的问题,来自官网翻译整理:
开启硬件加速之后的异常反应: 1.某些UI元素没有显示:可能是没有调用invalidate 2.某些UI元素没有更新:可能是没有调用invalidate 3.绘制不正确:可能使用了不支持硬件加速的操作, 需要关闭硬件加速或者绕过该操作 4.抛出异常:可能使用了不支持硬件加速的操作, 需要关闭硬件加速或者绕过该操作
上面的工程程序:
http://pan.baidu.com/s/1bq8D0U