魅族 安卓开发面试 问题整理(2017.12.13)

一. 安卓卡顿的原理是什么?是什么造成的?

Android 系统每 16ms 秒会发出一个 VSYNC 信号,触发对 UI 的渲染,如果每次都渲染成功,就能达到流畅画面要求的 60 fps;如果无法在 16 ms 内完成一次渲染,就会产生卡顿的感觉;

产生的原因:
1. Layout 内容过于复杂
2. Layout 层级过高
3. 动画执行次数过多
4. 大量的 GC 操作

参考资料:
http://hukai.me/android-performance-patterns/

二. 完整读过的安卓模块源码

另开文章补充。

三. 什么原因会造成普通内部类 Handler 不被回收,从而产生 OOM?

  1. Handler 发送了延时消息,在消息处理之前 Activity 结束;
  2. Handler 声明为静态成员。

四. 为何只有主线程能操作 UI?

这句话不正确,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

五. Retrofit 结果回调函数在哪个线程执行?

使用默认的 CallAdapter,回调函数在 UI 线程执行;
使用 RxJava,回调函数可自由切换线程执行。

六. Handler 发送消息到 MessageQueue 的动作在哪个线程执行?

Handler 发送消息到 MessageQueue 的 enqueueMessage 方法在创建 Handler 的线程执行。
(不确定)

七. Android 跨进程通信的方式

  1. Bundle
  2. 文件共享
  3. Messenger
  4. AIDL
  5. ContentProvider
  6. Socket

八. ListView 的优化方式

  1. 优化加载布局
  2. 优化加载组件

由于加载 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 协议,断点续传

HTTP 是一个属于应用层的面向对象的协议,主要特点可概括如下:

  1. 支持客户/服务器模式;
  2. 简单快速:客户向服务端发起请求时,只传送请求方式(GET,POST 等)和路径。由于 HTTP 协议简单,使得服务器程序规模小,所以通讯速度很快;
  3. 灵活:允许传输任何类型的数据对象;
  4. 无连接:限制每次连接只处理一个请求,等到客户的回应后,立即断开连接;
  5. 无状态:事务对于事务处理没有记忆能力;

断点续传

指的是在上传/下载时,将任务(一个文件或压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传/下载,如果碰到网络故障,可以从已经上传/下载的部分开始继续上传/下载未完成的部分,而没有必要从头开始上传/下载。可以节省时间,提高速度。

十. Android 绘制的三个过程

  1. measure:确定 View 的大小;
  2. layout:确定 View 的位置;
  3. draw:在屏幕上绘制 View。

十一. 自定义 View 注意事项

直接继承 View 的自定义控件需要重写 onMeasure 方法并设置 wrap_content 时的自身大小,否则在布局中使用 wrap_content 就相当于使用 match_parent。

十二. ViewGroup 的 onMeasure 是先测量自己,还是先遍历测量子 View?

ViewGroup 先遍历测量子 View,然后根据子 View 的结果来测量自己的大小。

你可能感兴趣的:(Android)