Android GPU加速渲染自定义View 性能改善<13>

常常在开发过程中使用自定义的View,而自定义的View的图形往往是onDraw里面实现的,这样就可能因为在父容器里面而因为父容器稍微的变更,就会重绘,重绘是需要很多内存消耗的,而且如果父容器有背景色,那么onDraw所画的一切图形色彩都是再父容器的基础上进行,从而导致某一个像素点上,进行了多次渲染,这是需要内存消耗的.专家的建议是:

我们可以先按照通常的方式把View上的元素按照从后到前的方式绘制出来,但是不直接显示到屏幕上,而是使用GPU预处理之后,再又GPU渲染到屏幕上,GPU可以对界面上的原始数据直接做旋转,设置透明度等等操作。使用GPU进行渲染,虽然第一次操作相比起直接绘制到屏幕上更加耗时,可是一旦原始纹理数据生成之后,接下去的操作就比较省时省力。

个人觉得GPU就像一个相机,将View的图形在第一次渲染的时候拍下来,生成一个底片,下次再视图更新的时候,要显示自定义的View,GPU就把底片冲洗一下,贴出来,就不需要再去重新绘制了,这样就节省了很多内存消耗:



具体操作事例如下:

<1> : 新建一个Android工程:

Android GPU加速渲染自定义View 性能改善<13>_第1张图片



<2> : 具体程序如下:

DurianMainActivity.java


package org.durian.duriangpuview;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;

import java.util.List;

public class DurianMainActivity extends ActionBarActivity {

    private ListView listView;

    private Button button;

    private DurianBaseAdapter mDurianBaseAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.durian_main);

        listView=(ListView)findViewById(android.R.id.list);
        listView.setBackgroundColor(Color.GRAY);

        mDurianBaseAdapter=new DurianBaseAdapter(this);
        listView.setAdapter(mDurianBaseAdapter);

        button=(Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDurianBaseAdapter.notifyDataSetChanged();
            }
        });

    }

    @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);
    }
}

DurianBaseAdapter.java
package org.durian.duriangpuview;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import org.durian.duriangpuview.view.DurianGPUAlphaView;

/**
 * Project name : DurianGPUView
 * Created by zhibao.liu on 2016/1/15.
 * Time : 11:58
 * Email [email protected]
 * Action : durian
 */
public class DurianBaseAdapter extends BaseAdapter implements ObjectAnimator.AnimatorListener {

    private final static String TAG="DurianBaseAdapter";
    private Context mContext;

    private LayoutInflater inflater;

    private ObjectAnimator objectAnimator=new ObjectAnimator();

    public DurianBaseAdapter(Context context){

        mContext=context;
        inflater=LayoutInflater.from(context);

    }
    @Override
    public int getCount() {
        return 150;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        Log.i(TAG,"*** getView position : "+position);

        if(convertView==null){

            Log.i(TAG,"getView position : "+position);
            convertView=inflater.inflate(R.layout.list_view,null);
            viewHolder.gpuAlphaView=(DurianGPUAlphaView)convertView.findViewById(R.id.durianview);

            objectAnimator=ObjectAnimator.ofFloat(viewHolder.gpuAlphaView, "rotationY", 360).setDuration(5000);
            viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_HARDWARE,null);
            objectAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {

                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_NONE,null);
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });

            objectAnimator.start();

//            ObjectAnimator.ofFloat(viewHolder.gpuAlphaView, "rotationY", 360).setDuration(2500).start();


        }

        return convertView;
    }


    private ViewHolder viewHolder=new ViewHolder();

    @Override
    public void onAnimationStart(Animator animation) {

    }

    @Override
    public void onAnimationEnd(Animator animation) {

    }

    @Override
    public void onAnimationCancel(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {

    }

    private class ViewHolder{
        DurianGPUAlphaView gpuAlphaView;
    }

}

DurianGPUAlphaView.java
package org.durian.duriangpuview.view;

import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import org.durian.duriangpuview.R;

/**
 * Project name : DurianGPUView
 * Created by zhibao.liu on 2016/1/15.
 * Time : 11:00
 * Email [email protected]
 * Action : durian
 */
public class DurianGPUAlphaView extends View {

    private final static String TAG="DurianGPUAlphaView";

    private Paint mPaint;

    private Bitmap bitmap;

    public DurianGPUAlphaView(Context context, AttributeSet attrs) {
        super(context, attrs);

        initView(context);

    }

    private void initView(Context context){

        mPaint=new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setTextSize(128);
        if(!isInEditMode()){
            Log.i(TAG,"not in edit mode !");
//            setLayerType(View.LAYER_TYPE_HARDWARE,null);

        }else{
            Log.i(TAG,"in edit mode !");
//            setLayerType(View.LAYER_TYPE_NONE,null);
        }

        bitmap= BitmapFactory.decodeResource(context.getResources(), R.drawable.view);

    }

    @Override
    public boolean hasOverlappingRendering() {
        return false;//super.hasOverlappingRendering();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

//        canvas.drawColor(0xff00ff00);

        canvas.drawBitmap(bitmap,25,0,null);

        canvas.drawText("DURIAN WORLD",300,175,mPaint);

    }

}


durian_main.xml

<?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"
    tools:context="org.durian.duriangpuview.DurianMainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="update list"/>

    <org.durian.duriangpuview.view.DurianGPUAlphaView
        android:id="@+id/gpuview"
        android:layout_width="wrap_content"
        android:layout_height="128dp" />
    
    <View
        android:background="#0000ff"
        android:layout_width="match_parent"
        android:layout_height="4dp"/>

    <ListView
        android:id="@+id/android:list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

list_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <org.durian.duriangpuview.view.DurianGPUAlphaView
        android:id="@+id/durianview"
        android:layout_width="wrap_content"
        android:layout_height="128dp"
        android:background="@drawable/shadows"/>

</LinearLayout>


shadows.xml 给自定义View套个阴影的效果

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_pressed="true">
        <layer-list>
            <item android:left="4dp" android:top="4dp"><shape>
                <solid android:color="#ff58bb52" />
                <corners android:radius="30dip" />
            </shape></item>
        </layer-list>
    </item>
    <item>
        <layer-list>
            <!-- 第一层 -->
            <item android:left="4dp" android:top="4dp"><shape>
                <solid android:color="#66000000" />
                <corners android:radius="30dip" />
                <!-- 描边 -->
                <stroke android:width="1dp" android:color="#ffffffff" />
            </shape></item>
            <!-- 第二层 -->
            <item android:bottom="4dp" android:right="4dp"><shape>
                <solid android:color="#ff58bb52" />
                <corners android:radius="30dip" />
                <!-- 描边 -->
                <stroke android:width="1dp" android:color="#ffffffff" />
            </shape></item>
        </layer-list></item>
</selector>

<3> : 具体处理如下:

a> : 如果确定自定View一定要GPU渲染,那么在自定义View中增加GPU渲染设置:

        if(!isInEditMode()){
            Log.i(TAG,"not in edit mode !");
//            setLayerType(View.LAYER_TYPE_HARDWARE,null);

        }else{
            Log.i(TAG,"in edit mode !");
//            setLayerType(View.LAYER_TYPE_NONE,null);
        }

把注释去掉.


b> : 在使用自定义View时,有很多地方需要做一下GPU加速渲染的操作,以保证运行流畅效果.比如在ListView中经常需要刷新:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {

        Log.i(TAG,"*** getView position : "+position);

        if(convertView==null){

            Log.i(TAG,"getView position : "+position);
            convertView=inflater.inflate(R.layout.list_view,null);
            viewHolder.gpuAlphaView=(DurianGPUAlphaView)convertView.findViewById(R.id.durianview);

            objectAnimator=ObjectAnimator.ofFloat(viewHolder.gpuAlphaView, "rotationY", 360).setDuration(5000);
            viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_HARDWARE,null);
            objectAnimator.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {

                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_NONE,null);
                }

                @Override
                public void onAnimationCancel(Animator animation) {

                }

                @Override
                public void onAnimationRepeat(Animator animation) {

                }
            });

            objectAnimator.start();

//            ObjectAnimator.ofFloat(viewHolder.gpuAlphaView, "rotationY", 360).setDuration(2500).start();


        }

        return convertView;
    }


核心代码:

objectAnimator=ObjectAnimator.ofFloat(viewHolder.gpuAlphaView, "rotationY", 360).setDuration(5000);
            viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_HARDWARE,null);
objectAnimator.start();


动画播放完了就取消:

@Override
                public void onAnimationEnd(Animator animation) {
                    viewHolder.gpuAlphaView.setLayerType(View.LAYER_TYPE_NONE,null);
                }


因为上面说了,GPU可以对界面上的原始数据直接做旋转,设置透明度等等操作!!!


c> : 当自定义View添加了阴影效果时:


@Override
    public boolean hasOverlappingRendering() {
        return false;//super.hasOverlappingRendering();
    }

返回为false改善之.因为:

另外一个例子是包含阴影区域的View,这种类型的View并不会出现我们前面提到的问题,因为他们并不存在层叠的关系


例子源代码,可以自行运行看效果,今天这个东西好像无法上图:

http://pan.baidu.com/s/1blpUeq























你可能感兴趣的:(Android GPU加速渲染自定义View 性能改善<13>)