列表、存储以及WebView 相关优化

2020-04-23 17:40:27 浏览数 (1)

列表优化

ListView 优化逻辑

  1. adapter中的getView方法中尽量少使用逻辑
  2. 尽最大可能避免GC
  3. 滑动的时候不载入图片
  4. ListViewscrollingCache和animateCache设置为false
  5. item的布局层级越少越好
  6. 使用ViewHolder
  7. 使用RecyclerView
  • adapter中的getView方法中尽量少使用逻辑

不要在你的getView()中写过多的逻辑代码,我们能够将这些代码放在别的地方。比如:

优化前的getView():

代码语言:javascript复制
<br />@Override
 
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
 
        Object current_event = mObjects.get(position);
 
        ViewHolder holder = null;
 
        if (convertView == null) {
 
                holder = new ViewHolder();
 
                convertView = inflater.inflate(R.layout.row_event, null);
 
                holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
 
                holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
 
                convertView.setTag(holder);
 
 
 
        } else {
 
                holder = (ViewHolder) convertView.getTag();
 
        }
 
 
 
       //在这里进行逻辑推断。这是有问题的 
 
        if (doesSomeComplexChecking()) {
 
                holder.ThreeDimention.setVisibility(View.VISIBLE);
 
        } else {
 
                holder.ThreeDimention.setVisibility(View.GONE); 
 
        }
 
 
 
        // 这是设置image的參数,每次getView方法运行时都会运行这段代码。这显然是有问题的
 
        RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
 
        holder.EventPoster.setLayoutParams(imageParams);
 
 
 
        return convertView;
 
}
 
 

优化后的getView():

代码语言:javascript复制
<br />@Override
 
public View getView(int position, View convertView, ViewGroup paramViewGroup) {
 
    Object object = mObjects.get(position);
 
    ViewHolder holder = null;
 
 
 
    if (convertView == null) {
 
            holder = new ViewHolder();
 
            convertView = inflater.inflate(R.layout.row_event, null);
 
            holder.ThreeDimension = (ImageView) convertView.findViewById(R.id.ThreeDim);
 
            holder.EventPoster = (ImageView) convertView.findViewById(R.id.EventPoster);
 
            //设置參数提到这里,仅仅有第一次的时候会运行,之后会复用 
 
            RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(measuredwidth, rowHeight);
 
            holder.EventPoster.setLayoutParams(imageParams);
 
            convertView.setTag(holder);
 
    } else {
 
            holder = (ViewHolder) convertView.getTag();
 
    }
 
 
 
    // 我们直接通过对象的getter方法取代刚才那些逻辑推断。那些逻辑推断放到别的地方去运行了
 
    holder.ThreeDimension.setVisibility(object.getVisibility());
 
 
 
    return convertView;
 
}
 
 
  • GC 垃圾回收器

当你创建了大量的对象的时候。GC就会频繁的运行。所以在getView()方法中不要创建非常多的对象。最好的优化是,不要在ViewHolder以外创建不论什么对象。假设你的你的log里面发现“GC has freed some memory”频繁出现的话。那你的程序肯定有问题了。

你能够检查一下:

  • item布局的层级是否太深
  • getView()方法中是否有大量对象存在
  • ListView的布局属性
    • 载入图片

假设你的ListView中须要显示从网络上下载的图片的话。我们不要在ListView滑动的时候载入图片,那样会使ListView变得卡顿,所以我们须要再监听器里面监听ListView的状态。假设滑动的时候,停止载入图片,假设没有滑动,则开始载入图片

代码语言:javascript复制
<br />listView.setOnScrollListener(new OnScrollListener() {
 
 
 
            @Override
 
            public void onScrollStateChanged(AbsListView listView, int scrollState) {
 
                    //停止载入图片 
 
                    if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
 
                            imageLoader.stopProcessingQueue();
 
                    } else {
 
                    //開始载入图片
 
                            imageLoader.startProcessingQueue();
 
                    }
 
            }
 
 
 
            @Override
 
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
 
                    // TODO Auto-generated method stub
 
 
 
            }
 
    });
 
 
  • 将ListView的scrollingCache和animateCache设置为false

scrollingCache: scrollingCache本质上是drawing cache,你能够让一个View将他自己的drawing保存在cache中(保存为一个bitmap),这样下次再显示View的时候就不用重画了,而是从cache中取出。默认情况下drawing cahce是禁用的。由于它太耗内存了,可是它确实比重画来的更加平滑。

而在ListView中,scrollingCache是默认开启的,我们能够手动将它关闭。

animateCache: ListView默认开启了animateCache,这会消耗大量的内存,因此会频繁调用GC,我们能够手动将它关闭掉

优化前的ListView

代码语言:javascript复制
<br />&lt;ListView
 
        android:id="@android:id/list"
 
        android:layout_width="match_parent"
 
        android:layout_height="wrap_content"
 
        android:cacheColorHint="#00000000"
 
        android:divider="@color/list_background_color"
 
        android:dividerHeight="0dp"
 
        android:listSelector="#00000000"
 
        android:smoothScrollbar="true"
 
        android:visibility="gone" /&gt; 
 
 

优化后的ListView

代码语言:javascript复制
<br />&lt;ListView
 
        android:id="@android:id/list"
 
        android:layout_width="match_parent"
 
        android:layout_height="wrap_content"
 
        android:divider="@color/list_background_color"
 
        android:dividerHeight="0dp"
 
        android:listSelector="#00000000"
 
        android:scrollingCache="false"
 
        android:animationCache="false"
 
        android:smoothScrollbar="true"
 
        android:visibility="gone" /&gt;
 
 
  • 降低item的布局的深度

我们应该尽量降低item布局深度,由于当滑动ListView的时候,这回直接导致測量与绘制,因此会浪费大量的时间。所以我们应该将一些不必要的布局嵌套关系去掉。降低item布局深度

  • 使用ViewHolder

这个大家应该非常熟悉了,可是不要小看这个ViewHolder,它能够大大提高我们ListView的性能

针对RecyclerView做了哪些优化

  1. onBindViewHolder

这个方法含义应该都知道是绑定数据,并且是在UI线程,所以要尽量在这个方法中少做一些业务处理

  1. 数据优化

采用android Support 包下的DIffUtil集合工具类结合RV分页加载会更加友好,节省性能

  1. item优化

减少item的View的层级,(pps:当然推荐把一个item自定义成一个View,如果有能力的话),如果item的高度固定的话可以设置setHasFixedSize(true),避免requestLayout浪费资源

  1. 使用RecycledViewPool

RecycledViewPool是对item进行缓存的,item相同的不同RV可以才使用这种方式进行性能提升

  1. Prefetch预取

这是在RV25.1.0及以上添加的新功能

  1. 资源回收

通过重写RecyclerView.onViewRecycled(holder)来合理的回收资源。

  1. 不要什么情况都用adapter.notifyDataSetChanged(),小范围修改可以试试adapter.notifyItemChanged(position)或者adapter.notifyItemRangeChanged(positionStart,itemcount)

TextView 优化

原因:面对复杂文本性能不佳 方案: - BoringLayout 单行 - StaticLayout 多行 - DynamicLayout 可编辑展示文本 - 展示类StaticLayout即可,性能优于DynamicLayout - 异步创建StaticLayout

代码语言:javascript复制
public class CustomTextView extends View {
 
    private String mText = "我是StaticLayout显示出来的文本";
    private TextPaint mTextPaint;
    private StaticLayout mStaticLayout;
 
    public CustomTextView(Context context) {
        super(context);
        initLabelView();
    }
 
    public CustomTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initLabelView();
    }
 
    private void initLabelView() {
        mTextPaint = new TextPaint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(16 * getResources().getDisplayMetrics().density);
        mTextPaint.setColor(Color.BLACK);
        final int width = (int) mTextPaint.measureText(mText);
 
        //异步创建StaticLayout
        Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                mStaticLayout = new StaticLayout(mText, mTextPaint, (int) width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0, false);
                postInvalidate();
            }
        });
    }
 
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if(mStaticLayout != null){
            canvas.save();
            canvas.translate(getPaddingLeft(), getPaddingTop());
            mStaticLayout.draw(canvas);
            canvas.restore();
        }
    }
}
 

字符串拼接优化

代码语言:javascript复制
   // 以下代码是为了演示字符串的拼接
        String msgOld = newsItem.title   newsItem.targetId;// 原有方式
 
        StringBuilder builder = new StringBuilder();
        builder.append(newsItem.title)
                .append(newsItem.targetId);// 建议使用方式,不要小看这点优化
        String msgNew = builder.toString();
 

存储优化

常规方案

  • 确保IO操作发送在非主线程
  • HOOK或者AOP辅助

SharedPreferences相关

  • 跨进程不安全。
  • 加载缓慢:初始化加载整个文件
  • 全量写入:每次改动都需要整体写入
  • 卡顿:补偿策略导致,由于提供了异步落盘的 apply 机制,在崩溃或者其他一些异常情况可能会导致数据丢失。
基于缺点改进
  1. 建议不要存储较大数据或者较多数据到SharedPreferences中;
  2. 频繁修改的数据修改后统一提交,而不是修改过后马上提交;
  3. 在跨进程通讯中不去使用SharedPreferences
  4. 键值对不宜过多;
使用腾讯MMKV 来替换SharedPreferences
  • mmp和文件锁保证数据的完整性
  • 增量写入,使用Protocol Buffer
  • 支持从SharedPreferences 迁移

引入方式,导包:implementation 'com.tencent:mmkv:1.0.17' 使用

代码语言:javascript复制
//在Application中进行初始化
MMKV.initialize(MyApplication.this);
//key值是times,值为100
MMKV.defaultMMKV().encode("times",100);
//获取key值对应的value值
int times = MMKV.defaultMMKV().decodeInt("times");
 

WebView 异常监控

监控屏幕是否白屏,白屏则webview有问题 确认白屏,所有像素一直则认为白屏

腾讯开源的轻量级框架VasSonic:轻量级高性能Hybrid框架 https://github.com/Tencent/VasSonic/wiki

WebView 白屏检测代码示例

代码语言:javascript复制
<br />public class TestBlankActivity extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.testblank);
        final TextView textView = findViewById(R.id.tv_testblank);
        textView.postDelayed(new Runnable() {
            @Override
            public void run() {
                boolean isBlank = BlankDetect.isBlank(textView);
                LogUtils.i("isBlank "  isBlank);
            }
        },2000);
 
    }
}
/**
 * WebView白屏检测
 */
public class BlankDetect {
 
    /**
     * 判断Bitmap是否都是一个颜色
     * @param bitmap
     * @return
     */
    public static boolean isBlank(View view) {
        Bitmap bitmap = getBitmapFromView(view);
 
        if (bitmap == null) {
            return true;
        }
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        if (width &gt; 0 &amp;&amp; height &gt; 0) {
            int originPix = bitmap.getPixel(0, 0);
            int[] target = new int[width];
            Arrays.fill(target, originPix);
            int[] source = new int[width];
            boolean isWhiteScreen = true;
            for (int col = 0; col &lt; height; col  ) {
                bitmap.getPixels(source, 0, width, 0, col, width, 1);
                //拿每个像素对比第一个像素点,如果不一样说明没有出现白屏,否则出现白屏
                if (!Arrays.equals(target, source)) {
                    //不是白屏
                    isWhiteScreen = false;
                    break;
                }
            }
            return isWhiteScreen;
        }
        return false;
    }
 
    /**
     * 从View获取转换到的Bitmap
     * @param view
     * @return
     */
    private static Bitmap getBitmapFromView(View view){
        Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        if (Build.VERSION.SDK_INT &gt;= 11) {
            view.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(view.getHeight(), View.MeasureSpec.EXACTLY));
            view.layout((int) view.getX(), (int) view.getY(), (int) view.getX()   view.getMeasuredWidth(), (int) view.getY()   view.getMeasuredHeight());
        } else {
            view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
        }
        view.draw(canvas);
        return bitmap;
    }
}
&lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:orientation="horizontal"&gt;
 
    &lt;TextView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:id="@ id/tv_testblank"/&gt;
 
&lt;/LinearLayout&gt;
 

0 人点赞