Android 自定义View过度绘制性能优化<7>

来源:互联网 发布:linux中scp命令 编辑:程序博客网 时间:2024/05/10 23:38

在自定义View 里面如果又有很多个子View相互重叠,比如,开发者在一张canvas上面画了N张图片,而N张图片存在重叠,那么很显然就会存在过度绘制的现象.

专家分析的意见如下:

<pre class="html" name="code">前面有提到过,对不可见的UI组件进行绘制更新会导致Overdraw。例如Nav Drawer从前置可见的Activity滑出之后,如果还继续绘制那些在Nav Drawer里面不可见的UI组件,这就导致了Overdraw。为了解决这个问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少Overdraw。那些Nav Drawer里面不可见的View就不会被执行浪费资源。但是不幸的是,对于那些过于复杂的自定义的View(通常重写了onDraw方法),Android系统无法检测在onDraw里面具体会执行什么操作,系统无法监控并自动优化,也就无法避免Overdraw了。但是我们可以通过canvas.clipRect()来帮助系统识别那些可见的区域。这个方法可以指定一块矩形区域,只有在这个区域内才会被绘制,其他的区域会被忽视。这个API可以很好的帮助那些有多组重叠组件的自定义View来控制显示的区域。同时clipRect方法还可以帮助节约CPU与GPU资源,在clipRect区域之外的绘制指令都不会被执行,那些部分内容在矩形区域内的组件,仍然会得到绘制。

其余要解决这样的问题原理还是比较简单,但是实现起来就很繁琐,因为它需要通过canvas.clipRect()方法将被覆盖的部分cut掉,比如说一张图片800*800的,但是能够显示出来的只有100*100,那么其他的700*700就要全部通过clipRect()方法调整绘制范围,只能够显示出100*100的部分,剩下的700*700全部被cut掉了.这样剩下的700*700的地方在绘制其他图片的时候,就不会出现重叠,如果不出现重叠,就不会出现过度绘制了(这话似乎有点绝对).

可以做一个android 工程测试一次:

<1> : 新建android 工程如下:


<2> : 所有的程序代码如下:

DurianMainActivity.java
package org.durian.durianviewoverdraw;import android.os.Bundle;import android.support.v7.app.ActionBarActivity;import android.view.Menu;import android.view.MenuItem;public class DurianMainActivity extends ActionBarActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.durian_main);    }    @Override    public boolean onCreateOptionsMenu(Menu menu) {        // Inflate the menu; this adds items to the action bar if it is present.        getMenuInflater().inflate(R.menu.menu_durian_main, menu);        return true;    }    @Override    public boolean onOptionsItemSelected(MenuItem item) {        // Handle action bar item clicks here. The action bar will        // automatically handle clicks on the Home/Up button, so long        // as you specify a parent activity in AndroidManifest.xml.        int id = item.getItemId();        //noinspection SimplifiableIfStatement        if (id == R.id.action_settings) {            return true;        }        return super.onOptionsItemSelected(item);    }}
DurianView.java
package org.durian.durianviewoverdraw.view;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Paint;import android.util.AttributeSet;import android.util.Log;import android.view.View;import org.durian.durianviewoverdraw.R;/** * Project name : DurianViewOverDraw * Created by zhibao.liu on 2016/1/11. * Time : 14:33 * Email warden_sprite@foxmail.com * Action : durian */public class DurianView extends View {    private final static String TAG="DurianView";    private Paint mpaint;    private Bitmap bitmap;    private Context mContext;    private boolean isReject=false;    public DurianView(Context context, AttributeSet attrs) {        super(context, attrs);        mContext=context;        initView();    }    private void initView(){        mpaint=new Paint();        mpaint.setColor(0x00ff00);        bitmap= BitmapFactory.decodeResource(mContext.getResources(), R.drawable.back);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        for(int i=0;i<2;i++) {            canvas.save();//            canvas.clipRect(10+i*180,10+i*180,500*(i+1),500*(i+1));            canvas.drawBitmap(bitmap, 10+i*180, 10+i*180, null);            /*if(i==1) {                isReject = canvas.quickReject(10 + i * 180, 10 + i * 180, 10 + i * 180+50, 10 + i * 180+50, Canvas.EdgeType.BW);                Log.i(TAG,"isReject : "+isReject);                isReject = canvas.quickReject(10 , 10 , 10 +180, 10 +180, Canvas.EdgeType.BW);                Log.i(TAG,"*** isReject : "+isReject);            }*/            canvas.restore();        }    }}

布局文件:

<?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:orientation="vertical">    <org.durian.durianviewoverdraw.view.DurianView        android:id="@+id/durianview"        android:layout_width="fill_parent"        android:layout_height="fill_parent" /></LinearLayout>

图片资源就自行放一张图片进去吧,稍微大一点的.


<3> : 运行结果:截取部分,因为我是那nexus平板测试的:

颜色就不需要解释了,蓝色这边是1X,绿色这边是2X,这个绿色是因为打开GPU overdraw显示出来的,并不是程序显示出来的.


<4> : 在DurianView代码中,我注释掉了几行,现在打开,看一看:


看以看出,裁剪掉的有绿色变成蓝色了,但是还是有一部分显示绿色,那是因为程序没有去裁剪(计算太繁琐了,所以没去弄了),这是因为画第一张图片,这样似乎不怎么好理解,我重新把DurianView程序调整一下:

package org.durian.durianviewoverdraw.view;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Paint;import android.util.AttributeSet;import android.util.Log;import android.view.View;import org.durian.durianviewoverdraw.R;/** * Project name : DurianViewOverDraw * Created by zhibao.liu on 2016/1/11. * Time : 14:33 * Email warden_sprite@foxmail.com * Action : durian */public class DurianView extends View {    private final static String TAG="DurianView";    private Paint mpaint;    private Bitmap bitmap;    private Context mContext;    private boolean isReject=false;    public DurianView(Context context, AttributeSet attrs) {        super(context, attrs);        mContext=context;        initView();    }    private void initView(){        mpaint=new Paint();        mpaint.setColor(0x00ff00);        bitmap= BitmapFactory.decodeResource(mContext.getResources(), R.drawable.back);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        for(int i=0;i<2;i++) {            canvas.save();            canvas.clipRect(10+i*250,100,10+250*(i+1),500*2);            canvas.drawBitmap(bitmap, 10+i*250, 100, null);            /*if(i==1) {                isReject = canvas.quickReject(10 + i * 180, 10 + i * 180, 10 + i * 180+50, 10 + i * 180+50, Canvas.EdgeType.BW);                Log.i(TAG,"isReject : "+isReject);                isReject = canvas.quickReject(10 , 10 , 10 +180, 10 +180, Canvas.EdgeType.BW);                Log.i(TAG,"*** isReject : "+isReject);            }*/            canvas.restore();        }    }}

运行结果:

如果不加裁剪:

一看就出现2X重绘了.


如果将程序改一下,划出4张来,把for循环里面的i<2,改为i<4即可,同时把裁剪那一行代码注释掉:


就很容看到1X,2X,3X,4X了.


然后将裁剪那一行代码释放出来:

canvas.clipRect(10+i*250,100,10+250*(i+1),500*2);

就不会存在了,全部是1X了,从而消除了过度绘制.


专家还提到canvas.quickReject()方法

if(i==1) {                isReject = canvas.quickReject(10 + i * 180, 10 + i * 180, 10 + i * 180+50, 10 + i * 180+50, Canvas.EdgeType.BW);                Log.i(TAG,"isReject : "+isReject);                isReject = canvas.quickReject(10 , 10 , 10 +180, 10 +180, Canvas.EdgeType.BW);                Log.i(TAG,"*** isReject : "+isReject);            }

在上面的基础上,注释去掉.


也就是说如果图形方框没有存在重叠,返回的为true.

下面把裁剪去掉:

可以通过这个判断某个Rect区域是否存在重叠,或者所谓的子图形有边界冲突.














0 0
原创粉丝点击