Android 系统每 16ms 秒会发出一个 VSYNC 信号,触发对 UI 的渲染,如果每次都渲染成功,就能达到流畅画面要求的 60 fps;如果无法在 16 ms 内完成一次渲染,就会产生卡顿的感觉;
产生的原因:
1. Layout 内容过于复杂
2. Layout 层级过高
3. 动画执行次数过多
4. 大量的 GC 操作
参考资料:
http://hukai.me/android-performance-patterns/
另开文章补充。
这句话不正确,Android 是“建议”在主线程操作 UI,并不是“只有”主线程能操作 UI。
首先,Android 中操作 UI 通常涉及到 View 的 invalidate 和 requestLayout 方法,这两个方法最终会调用 ViewRootImpl 的 invalidateChild 和 requestLayout 方法,这两个方法中都调用了 checkThread 方法,该方法判断当前线程是否等于 mThread,如果不是则抛出我们熟悉的异常 CalledFromWrongThreadException(“Only the original thread that created a view hierarchy can touch its views.”);
mThread 是在 ViewRootImpl 的构造方法中赋值的,在 WindowManagerImpl 的 addView 方法中存在 ViewRootImpl 构造方法的调用;
无论是 Activity 还是 Dialog 的显示,还是通过 WindowManager 直接添加窗口,最终都会调用 WindowManagerImpl 的 addView 方法。其中 Activity 对于 addView 的调用被封装起来了(setContentView 仅仅是设置 Activity 对应的视图),因此默认情况下,显示一个 Activity,WindowManagerImpl 的 addView 方法在 UI 线程中执行,mThread 为 UI 线程,因此只能在 UI 线程中操作 Activity。
然而,如果在子线程中调用 WindowManagerImpl 的 addView 方法,那么便可以在子线程中操作该 View:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
MyDialog dialog = new MyDialog(MainActivity.this);
// show 方法会调用 WindowManagerImpl 的 addView 方法
dialog.show();
Looper.loop();
}
}).start();
Android 建议在主线程操作 UI 的原因:保证线程同步。
参考资料:
http://blog.csdn.net/junhzhan/article/details/50757302
https://www.zhihu.com/question/24764972
使用默认的 CallAdapter,回调函数在 UI 线程执行;
使用 RxJava,回调函数可自由切换线程执行。
Handler 发送消息到 MessageQueue 的 enqueueMessage 方法在创建 Handler 的线程执行。
(不确定)
由于加载 ListView 的每一项都会调用 getView 方法,getView 方法中需要调用 LayoutInflater#inflate 方法加载布局,还需要调用 View#findViewById 方法查找布局中的控件。如果 getView 方法每次都要调用 LayoutInflater#inflate 和 View#findViewById 方法,则会影响 ListView 的性能;
因此,可以利用 View 的缓存 convertView 配合使用 ViewHolder,优化加载的布局和组件:
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
Person person = getItem(position);
View view;
ViewHolder viewHolder;
if (convertView == null) {
view = LayoutInflater.from(getContext()).inflate(resourceId, null);
viewHolder = new ViewHolder();
viewHolder.nameTv = (TextView) view.findViewById(R.id.name_tv);
viewHolder.powerTv = (TextView) view.findViewById(R.id.power_tv);
view.setTag(viewHolder);
} else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
viewHolder.nameTv.setText(person.getName());
viewHolder.powerTv.setText(person.getPower());
return view;
}
class ViewHolder {
TextView nameTv;
TextView powerTv;
}
注意,convertView 并不是首次加载 View 之后就存在,而是存在于 ScrapView 之中。
初次加载时,屏幕上可见的 ListView 项是 ActionView,之后通过上拉或下拉而离开屏幕可见范围的项是 ScrapView;
位于 ActionView 中的项不会使用缓存 ConvertView,必须调用 LayoutInflater#inflate 和 View#findViewById 方法;由于上拉或下拉而离开屏幕可见范围的 ScrapView,会将自身作为 ConvertView 参数,加入到 RecycleBin 中,而新进入屏幕可见范围的项,会从 RecycleBin 中获取 ConvertView 作为缓存(如果没有,则需手动加载);
因此,经过优化后的 ListView,最多只需加载 N + 1 次布局和组件(有可能最后一项显示不全,导致第一项还没完全离开之前,新的一项就显示出来了),N 为屏幕能够显示的 ListView 的项数。
参考资料:http://www.cnblogs.com/yuhanghzsd/p/5595532.html
HTTP 是一个属于应用层的面向对象的协议,主要特点可概括如下:
断点续传
指的是在上传/下载时,将任务(一个文件或压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传/下载,如果碰到网络故障,可以从已经上传/下载的部分开始继续上传/下载未完成的部分,而没有必要从头开始上传/下载。可以节省时间,提高速度。
直接继承 View 的自定义控件需要重写 onMeasure 方法并设置 wrap_content 时的自身大小,否则在布局中使用 wrap_content 就相当于使用 match_parent。
ViewGroup 先遍历测量子 View,然后根据子 View 的结果来测量自己的大小。