Android应用开发岗 面试汇总-Android进阶篇

2022-03-25  本文已影响0人  hahaoop

背景

最近在准备面试,结合之前的工作经验和近期在网上收集的一些面试资料,准备将Android开发岗位的知识点做一个系统的梳理,整理成一个系列:Android应用开发岗 面试汇总。本系列将分为以下几个大模块:
Java基础篇Java进阶篇常见设计模式
Android基础篇Android进阶篇性能优化
网络相关数据结构与算法
常用开源库、Kotlin、Jetpack

注1:以上文章将陆续更新,直到我找到满意的工作为止,有跳转链接的表示已发表的文章。
注2:该系列属于个人的总结和网上东拼西凑的结果,每个知识点的内容并不一定完整,有不正确的地方欢迎批评指正。
注3:部分摘抄较多的段落或有注明出处。如有侵权,请联系本人进行删除。

1、Handler原理

1、使用

一般用来在子线程中做耗时操作,执行完后通过Handler来发送执行结果到主线程。
即:在主线程中实例化一个Handler,拿到引用后,在子线程中通过这个引用发送消息到主线程。

//一般在Activity中实例化Handler,等同于在主线程中声明Handler,new Handler(Looper.getLooper())
private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case UPDATE:
                    tv.setText(String.valueOf(msg.arg1));
                    break;
            }
        }
    };
public void begin(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                    try {
                        Thread.sleep(1000);//休眠1秒,模拟耗时操作
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Message msg = new Message();
                    msg.what = UPDATE;
                    msg.arg1 = i;
                    handler.sendMessage(msg);
            }
        }).start();
    }

2、Handler初始化流程

构造方法的参数

Handler在构造方法中初始化Looper和MessageQueue

ActivityThread与Looper 的调用

class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }

总结(UI线程中的Handler)

ActivityThread类中的main方法,调用
--1、Looper.prepareMainLooper()
--2、prepareMainLooper 方法中调用prepare方法初始化Looper对象(主线程初始化,则为MainLooper),将该对象塞入ThreadLocal<Looper> sThreadLocal变量中
--3、初始化主线程的Handler,该Handler会放入到线程池中
--4、调用Looper.loop(),开始消息循环

Handler的工作流程

1、某个线程的handler实例(一般为UI线程)调用handler.sendMessage(msg)(任意线程中调用),将Message对象加入到Looper的MessageQueue中(MessageQueue的实例在Looper中,Handler中拿到的是它的引用),Looper的loop方法是一个死循环(for(;;)),会不停的消费掉msg对象,消费掉一个,就会调用msg.target.dispatchMessage(msg),同时将msg移出队列。target即为handler的引用。
2、Handler的dispatchMessage方法中,调用了handleMessage的回调方法,供外部处理线程中生成的msg。

4cf3b5423e960035a18fb6d75d3c6440.png

参考链接

// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        // ...
    }
}
// Handler.java
public final boolean postDelayed(Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    // 传入的 time 是 uptimeMillis + delayMillis
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    // ...
    return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 调用 MessageQueue.enqueueMessage
    return queue.enqueueMessage(msg, uptimeMillis);
}

最终是通过MessageQueue的next方法来实现的,等待时间调的是native的epoll_wait 来进行等待
1.将我们传入的延迟时间转化成距离开机时间的毫秒数
2.MessageQueue 中根据上一步转化的时间进行顺序排序
3.在 MessageQueue.next 获取消息时,对比当前时间(now)和第一步转化的时间(when),如果 now < when,则通过 epoll_wait 的 timeout 进行等待
4.如果该消息需要等待,会进行 idel handlers 的执行,执行完以后会再去检查此消息是否可以执行

2、Binder相关

Android 是基于Linux系统来实现的,因此,我们有必要来了解一下,为什么Android 不使用Linux本身有的进程通信机制,而是要自己撸一个Binder 这玩意来实现进程间通信。
首先,需要简要的了解下Linux 进程间通信的几种方式

1、Linux IPC机制

image.png
1、管道通信的特性
管道通信是一种1v1 的通信方式,相对来说比较安全的。缺点是:数据只能单向传输。

2、共享内存的特性
共享内存就是允许两个或多个进程共享一定的存储区。当共享的这块存储区中的数据发生改变,所有共享这块存储区的进程都会察觉数据的改变,因为数据不需要在客户端和服务端进行数据拷贝,数据直接写到内存,不用若干次数据拷贝,所以这是最快的IPC。

3、Socket 通信的特性
Socket通信是双向的通信,客户端与服务端要建立连接,然后读写数据,相当于在两个进程间各自拷贝数据,然后传输数据,这个效率是很慢的。

2、Android IPC通信:Binder

1、内核空间和用户空间

内核空间(内核进程):操作系统所占用的内存区域 -------只有一份
用户空间(用户进程):用户进程所在的内存区域 ---------多份
Q:为什么要这么划分?
A:使用内核空间和用户空间这种分开来划分,可以做到,每个APP(用户空间)不会影响其他APP,也不会造成系统的崩溃。

2、物理地址和虚拟地址

3、Binder IPC 通信模型

以下是Binder IPC 通信模型的经典图,原本两个进程间数据的交互需要两次拷贝(发送数据方将数据拷贝到内核空间,然后内核空间再将数据拷贝到接收方),由于使用了mmap(内存映射)就减少了一次数据接收进程的数据拷贝(也可以说是服务端)。这样Binder只需要在数据发送进程(客户端)实现一次拷贝数据到内核空间即可。

image.png
参考链接

4、Android中跨进程通信的几种方式

四大组件:Activity、广播、ContentProvider、Service(AIDL)
本地文件的读写,两个不同程序对同一个文件进行读写

MVC、MVP、MVVM的特性:

一篇不错的总结,需要单独消化

MVC:

Android提供的Activity、xml就是经典的MVC模式

MVP:

以上两种模式的优点:

缺点:

MVVM:

MVVM 模式改动在于中间的 Presenter 改为 ViewModel,MVVM 同样将代码划分为三个部分:

在实现细节上,View 和 Presenter 从双向依赖变成 View 可以向 ViewModel 发指令,但 ViewModel 不会直接向 View 回调,而是让 View 通过观察者的模式去监听数据的变化,有效规避了 MVP 双向依赖的缺点。但 MVVM 本身也存在一些缺点:

image

DataBinding、ViewModel 和 LiveData 等组件是 Google 为了帮助我们实现 MVVM 模式提供的架构组件,它们并不是 MVVM 的本质,只是实现上的工具。
Lifecycle: 生命周期状态回调;
LiveData: 可观察的数据存储类;
databinding: 可以自动同步 UI 和 data,不用再 findviewById();
ViewModel: 存储界面相关的数据,这些数据不会在手机旋转等配置改变时丢失。

App启动流程

启动模式相关(launchMode)

Q:当手机执行菜单键,查看最近任务时,会出现多个正在运行的应用列表,此时这个列表里显示的是什么?
A:显示的是一个个的Task,用户可见的是Task栈顶的Activity截图。

事件传递机制

事件在Android中的传递顺序

事件的传递规则

一个点击事件,或者说触摸事件,被封装为了一个MotionEvent。事件的分发主要由三个重要的方法来完成:
1、分发:dispatchTouchEvent;
2、拦截:onInterceptTouchEvent;
3、处理:onTouchEvent;
如果是ViewGroup容器类view,则以上三个方法都会用到。但是如果是View类型的,不能包含子view,那就没有第二个拦截方法,因为没有子view的话,拦截方法的就是多余的,只有ViewGroup才会有拦截。

View的绘制流程

Android 中 Activity 是作为应用程序的载体存在,代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当 Activity 启动时,我们会通过 setContentView 方法来设置一个内容视图,这个内容视图就是用户看到的界面。


image.png

整体流程

当一个应用启动时,会启动一个主 Activity,Android 系统会根据 Activity 的布局来对它进行绘制。绘制会从根视图 ViewRoot 的 performTraversals() 方法开始,从上到下遍历整个视图树,每个 View 控制负责绘制自己,而 ViewGroup 还需要负责通知自己的子 View 进行绘制操作。视图操作的过程可以分为三个步骤,分别是测量(Measure)、布局(Layout)和绘制(Draw)

Measure

Layout

Draw

  public void draw(Canvas canvas) {
    ...
    // Step 1, draw the background, if needed
    if (!dirtyOpaque) {
      drawBackground(canvas);
    }
    ...
    // Step 2, save the canvas' layers
    saveCount = canvas.getSaveCount();
    ...
    // Step 3, draw the content
    if (!dirtyOpaque) onDraw(canvas);

    // Step 4, draw the children
    dispatchDraw(canvas);

    // Step 5, draw the fade effect and restore layers
    canvas.drawRect(left, top, right, top + length, p);
    ...
    canvas.restoreToCount(saveCount);
    ...
    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);
  }

链接:https://www.jianshu.com/p/c151efe22d0d

解决滑动冲突的方式

事件分发机制:Android中是从外向内分发,从内向外消耗的,记住这一点滑动冲突就很好解决。

自定义view

HenCoder的教学视频和笔记

概述

自定义绘制知识的4个级别

链接

上一篇下一篇

猜你喜欢

热点阅读