学习网上面以为厉害的博主的文章,自己跟着学习的节奏,也一步一步的进行学着分析,加深印象,更重要的是自己能够熟悉里面的细节部分,细节决定一些性能.
首先写了一个测试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 warden_sprite@foxmail.com
* @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 warden_sprite@foxmail.com
* @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);
添加硬件加速的设置,View将在执行onDraw之后,自动把缓存保存为一张图片,并通过GPU来重绘不同的角度,但是由于硬件加速是需要付出代价的,它会消耗video memory资源的,所以仅仅在用户触发scrolling的时候使用LAYER_TYPE_HARDWARE,在其他时候,使用LAYER_TYPE_NONE,这话是专家的.
硬件加速可能遭遇的问题,来自官网翻译整理:
开启硬件加速之后的异常反应:
1.某些UI元素没有显示:可能是没有调用invalidate
2.某些UI元素没有更新:可能是没有调用invalidate
3.绘制不正确:可能使用了不支持硬件加速的操作, 需要关闭硬件加速或者绕过该操作
4.抛出异常:可能使用了不支持硬件加速的操作, 需要关闭硬件加速或者绕过该操作
上面的工程程序:
http://pan.baidu.com/s/1bq8D0U