列表优化
ListView 优化逻辑
- 在
adapter
中的getView
方法中尽量少使用逻辑 - 尽最大可能避免GC
- 滑动的时候不载入图片
- 将
ListView
的scrollingCache和animateCache
设置为false -
item
的布局层级越少越好 - 使用
ViewHolder
- 使用
RecyclerView
- 在
adapter
中的getView
方法中尽量少使用逻辑
不要在你的getView()
中写过多的逻辑代码,我们能够将这些代码放在别的地方。比如:
优化前的getView():
<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():
<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
<br /><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" />
优化后的ListView
<br /><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" />
- 降低
item
的布局的深度
我们应该尽量降低item布局深度,由于当滑动ListView的时候,这回直接导致測量与绘制,因此会浪费大量的时间。所以我们应该将一些不必要的布局嵌套关系去掉。降低item布局深度
- 使用
ViewHolder
这个大家应该非常熟悉了,可是不要小看这个ViewHolder
,它能够大大提高我们ListView
的性能
针对RecyclerView做了哪些优化
onBindViewHolder
这个方法含义应该都知道是绑定数据,并且是在UI线程,所以要尽量在这个方法中少做一些业务处理
- 数据优化
采用android Support
包下的DIffUtil集合工具类结合RV分页加载会更加友好,节省性能
item
优化
减少item的View的层级,(pps:当然推荐把一个item自定义成一个View,如果有能力的话),如果item的高度固定的话可以设置setHasFixedSize(true)
,避免requestLayout
浪费资源
- 使用
RecycledViewPool
RecycledViewPool
是对item进行缓存的,item相同的不同RV可以才使用这种方式进行性能提升
Prefetch
预取
这是在RV25.1.0及以上添加的新功能
- 资源回收
通过重写RecyclerView.onViewRecycled(holder)
来合理的回收资源。
- 不要什么情况都用
adapter.notifyDataSetChanged()
,小范围修改可以试试adapter.notifyItemChanged(position)
或者adapter.notifyItemRangeChanged(positionStart,itemcount)
TextView 优化
原因:面对复杂文本性能不佳
方案:
- BoringLayout
单行
- StaticLayout
多行
- DynamicLayout
可编辑展示文本
- 展示类StaticLayout
即可,性能优于DynamicLayout
- 异步创建StaticLayout
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 机制,在崩溃或者其他一些异常情况可能会导致数据丢失。
基于缺点改进
- 建议不要存储较大数据或者较多数据到
SharedPreferences
中; - 频繁修改的数据修改后统一提交,而不是修改过后马上提交;
- 在跨进程通讯中不去使用
SharedPreferences
; - 键值对不宜过多;
使用腾讯MMKV 来替换SharedPreferences
- mmp和文件锁保证数据的完整性
- 增量写入,使用Protocol Buffer
- 支持从SharedPreferences 迁移
引入方式,导包:implementation 'com.tencent:mmkv:1.0.17'
使用
//在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 > 0 && height > 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 < 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 >= 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;
}
}
<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">
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:id="@ id/tv_testblank"/>
</LinearLayout>