极客班作业——视图优化

来源:互联网 发布:java web 配置log4j2 编辑:程序博客网 时间:2024/06/06 12:54

作业项目地址:https://github.com/lzyzsd/AndroidUIPorblems

这次主要使用开发者选项中的show overdraw选项来排除问题。

第一部分——MainActivity界面

在打开开发者选项中的ShowOverDraw选项后MainActivity的界面是这个样子(见下图)。可以看到主界面存在多次绘制现象,最高为4次绘制(红色部分)。回到代码进行分析。


查看MainActivity的布局文件activity_main的代码:

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    android:orientation="vertical"    tools:context="com.github.lzyzsd.androiduiproblems.MainActivity"    android:background="#2e2e2e">    <Button        android:id="@+id/btn_show_overdraw"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center_horizontal"        android:textSize="20dp"        android:text="OverDrawView"/>    <Button        android:id="@+id/btn_busy_on_draw"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center_horizontal"        android:textSize="20dp"        android:text="BusyOnDraw"/>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="20dp"        android:background="#2e2e2e">        <ImageView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@mipmap/ic_launcher"/>        <LinearLayout            android:layout_width="0dp"            android:layout_weight="1"            android:layout_height="match_parent"            android:background="#6c6c6c">            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="center_vertical"                android:textSize="20sp"                android:text="This is test"/>        </LinearLayout>    </LinearLayout></LinearLayout>
可以看到在LinearLayout中都有设置background这个属性。android里如果对控件添加了background属性就会使控件多绘制一次,这样很容易造成过渡绘制问题。所以我们把background属性去掉,让控件背景默认为透明的。修改后效果如图:


现在界面上还存在1倍过度绘制问题。因为android会为每个window设置默认背景,所以我们就修改代码把默认背景去掉。在MainActivity的onCreate方法中添加如下代码:

@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        getWindow().setBackgroundDrawable(null);        setContentView(R.layout.activity_main);
重新运行程序后效果如下:

虽然问题解决了,但是界面展示效果大打折扣,所以window默认背景我们就不去掉了。

再分析布局代码,发现以下代码中存在多余的嵌套

 <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="20dp"        android:background="#2e2e2e">        <ImageView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@mipmap/ic_launcher"/>        <LinearLayout            android:layout_width="0dp"            android:layout_weight="1"            android:layout_height="match_parent"            android:background="#6c6c6c">            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="center_vertical"                android:textSize="20sp"                android:text="This is test"/>        </LinearLayout>    </LinearLayout>
为了使视图绘制时间减少,优化性能,同时达到相同的展示效果,故减少一个LinearLayout层级,最终修改后布局代码如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    android:orientation="vertical"    tools:context="com.github.lzyzsd.androiduiproblems.MainActivity"    >    <Button        android:id="@+id/btn_show_overdraw"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center_horizontal"        android:textSize="20dp"        android:text="OverDrawView"/>    <Button        android:id="@+id/btn_busy_on_draw"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center_horizontal"        android:textSize="20dp"        android:text="BusyOnDraw"/>    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="horizontal"        android:layout_marginTop="20dp"        >        <ImageView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@mipmap/ic_launcher"/>        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_gravity="center_vertical"            android:textSize="20sp"            android:text="This is test"/>    </LinearLayout></LinearLayout>

第二部分——OverDrawViewActivity界面

OverDrawViewActivity界面初次打开效果如下图:


可以看到存在很明显的过度绘制问题。

因为OverDrawViewActivity界面的布局文件只有一个OverDrawView的自定义控件,所以我们需要分析自定义控件的代码来解决问题,自定义控件代码如下:

public class OverDrawView extends View {    Paint mPaint = new Paint();    public OverDrawView(Context context) {        super(context);    }    public OverDrawView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public OverDrawView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    public OverDrawView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);    }    private void init() {        mPaint.setStyle(Paint.Style.FILL);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        int width = getWidth();        int height = getHeight();        mPaint.setColor(Color.GRAY);        canvas.drawRect(0, 0, width, height, mPaint);        mPaint.setColor(Color.CYAN);        canvas.drawRect(0, height/4, width, height, mPaint);        mPaint.setColor(Color.DKGRAY);        canvas.drawRect(0, height/3, width, height, mPaint);        mPaint.setColor(Color.LTGRAY);        canvas.drawRect(0, height/2, width, height, mPaint);    }}
在onDraw方法中一共调用了四次canvas.drawRect()方法,进行了四次矩形绘制,但是四次绘制都会在一些区域进行重复绘制,有些区域甚至重复绘制了四次。因为从代码来看,后绘制的矩形会遮盖掉前一次绘制的矩形和自己重复的地方,所以我们只需修改代码将各自绘制的区域进行重新分配,是互相之间没有交集,避免过度绘制。修改后代码如下:
public class OverDrawView extends View {    Paint mPaint = new Paint();    public OverDrawView(Context context) {        super(context);    }    public OverDrawView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public OverDrawView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    public OverDrawView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);    }    private void init() {        mPaint.setStyle(Paint.Style.FILL);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        int width = getWidth();        int height = getHeight();        mPaint.setColor(Color.GRAY);        canvas.drawRect(0, 0, width, height/4, mPaint);        mPaint.setColor(Color.CYAN);        canvas.drawRect(0, height/4, width, height/3, mPaint);        mPaint.setColor(Color.DKGRAY);        canvas.drawRect(0, height/3, width, height/2, mPaint);        mPaint.setColor(Color.LTGRAY);        canvas.drawRect(0, height/2, width, height, mPaint);    }}
修改后重新运行程序,OverDrawViewActivity界面效果如下图:


可以看到前面的大面积红色区域已经没有了,但界面仍存在1次过度绘制问题,所以我们在OverDrawViewActivity的onCreate方法中添加下面这行代码:

getWindow().setBackgroundDrawable(null);
这样window的默认背景被设置为null,重新运行后效果如下图:


第三部分——BusyOnDrawActivity界面

BusyOnDrawActivity界面展示如下图:


查看代码后发现BusyOnDrawActivity界面布局是由大量的自定义控件组成的。页面主要是用ScrollView和LinearLayout来实现布局,没有使用RelativeLayout,同时也没有过度绘制现象。但是在主页面点击BUSYONDRAW按钮跳转到BusyOnDrawActivity时,页面出现了明显的卡顿,说明BusyOnDrawActivity页面绘制时间过长(PS:由于本人的Genymotion出了点问题无法用虚拟机,只能使用真机来测试,而我的真机的GPU Profiler中没有On screen as bars这个选项,所以就没有绘制时间的相关截图。),自定义控件的绘制代码性能有待优化。

于是查看自定义控件BusyOnDrawView代码如下:

public class BusyOnDrawView extends View {    public BusyOnDrawView(Context context) {        super(context);    }    public BusyOnDrawView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public BusyOnDrawView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    public BusyOnDrawView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        for (int i = 0; i < 1000; i++) {            System.out.println("canvas = [" + canvas + "]" + i);        }        Paint paint = new Paint();        paint.setColor(Color.RED);        paint.setStyle(Paint.Style.STROKE);        paint.setStrokeWidth(4);        int radius = Math.min(getWidth(), getHeight()) / 2;        canvas.drawCircle(getWidth()/2, getHeight()/2, radius, paint);    }}
发现两个主要问题。第一,在onDraw方法中创建Paint对象,因为onDraw方法会被系统多次调用所以最好不要在onDraw方法中创建对象,避免大量的内存使用从而导致GC,影响程序使用性能;第二,在onDraw方法中有一个for循环,而且这个for循环很耗时(循环了1000次),还创建大量的StringBuilder对象。这个for循环严重影响了自定义控件的绘制效率,使BusyOnDrawActivity界面绘制时间大大增加,导致页面出现卡顿现象。

解决方法是1.在onDraw方法外创建私有Paint对象。2.去掉这个for循环,在这里这个for循环所做的事是无意义的。最后修改代码如下:

public class BusyOnDrawView extends View {    private Paint paint = new Paint();    public BusyOnDrawView(Context context) {        super(context);    }    public BusyOnDrawView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public BusyOnDrawView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @TargetApi(Build.VERSION_CODES.LOLLIPOP)    public BusyOnDrawView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);                paint.setColor(Color.RED);        paint.setStyle(Paint.Style.STROKE);        paint.setStrokeWidth(4);        int radius = Math.min(getWidth(), getHeight()) / 2;        canvas.drawCircle(getWidth()/2, getHeight()/2, radius, paint);    }}

重新启动测试后,界面跳转变得非常流畅,没有卡顿现象出现。

如果在onDraw方法中一定要做耗时操作的话,建议使用线程来完成耗时操作,但同时会使代码逻辑变得很复杂或带来其他性能上的问题(比如onDraw方法中不要创建对象),所以最好不要在onDraw里做耗时操作。

0 0
原创粉丝点击