Android 高级面试-5:四大组件、系统源码等

一、四大组件

1.1 Activity

  • Q:在两个 Activity 之间传递对象还须要注意什么呢?

对象的大小。Intent 中的 Bundle 是使用 Binder 机制进行数据传送的。能使用的 Binder 的缓冲区是有大小限制的(有些手机是 2 M),而一个进程默认有 16 个 Binder 线程,因此一个线程能占用的缓冲区就更小了(有人之前作过测试,大约一个线程能够占用 128 KB)。可使用 EventBus 来传递数据,而不是直接使用 Intent 进行传递。java

  • Q:onSaveInstanceState() 和 onRestoreInstanceState()

当 Activity 被销毁的时候回调用 onSaveInstanceState() 方法来存储当前的状态。这样当 Activity 被重建的时候,能够在 onCreate()onRestoreInstanceState() 中恢复状态。android

对于 targetAPI 为 28 及之后的应用,该方法会在 onStop() 方法以后调用,对于以前的设备,这方法会在 onStop() 以前调用,可是没法肯定是在 onPause() 以前仍是以后调用。c++

onRestoreInstanceState() 方法用来恢复以前存储的状态,它会在 onStart()onPostCreate() 之间被调用。此外,你也能够直接在 onCreate() 方法中进行恢复,可是基于这个方法调用的时机,若是有特别需求,能够在这个方法中进行处理。git

  • Q:SingleTask 启动模式
  • Q:Activity 启动模式
  1. standard:默认,每次启动的时候会建立一个新的实例,而且被建立的实例所在的栈与启动它的 Activity 是同一个栈。好比,A 启动了 B,那么 B 将会与 A 处在同一个栈。假如,咱们使用 Application 的 Context 启动一个 Activity 的时候会抛出异常,这是由于新启动的 Activity 不知道本身将会处于哪一个栈。能够在启动 Activity 的时候使用 FLAG_ACTIVITY_NEW_TASK。这样新启动的 Acitivyt 将会建立一个新的栈。
  2. singleTop:栈顶复用,若是将要启动的 Activity 已经位于栈顶,那么将会复用栈顶的 Activity,而且会调用它的 onNewIntent()。常见的应用场景是从通知打开 Activity 时。
  3. singleTask:单例,若是启动它的任务栈中存在该 Activity,那么将会复用该 Activity,而且会将栈内的、它之上的全部的 Activity 清理出去,以使得该 Activity 位于栈顶。常见的应用场景是启动页面、购物界面、确认订单界面和付款界面等。
  4. singleInstance:这种启动模式会在启动的时候为其指定一个单独的栈来执行。若是用一样的intent 再次启动这个 Activity,那么这个 Activity 会被调到前台,而且会调用其 onNewIntent() 方法。
  • Q:下拉状态栏是否是影响 Activity 的生命周期,若是在 onStop() 的时候作了网络请求,onResume() 的时候怎么恢复
  • Q:前台切换到后台,而后再回到前台,Activity 生命周期回调方法。弹出 Dialog,生命值周期回调方法。
  • Q:Activity 生命周期
  • Q:Activity 上有 Dialog 的时候按 Home 键时的生命周期
  • Q:横竖屏切换的时候,Activity 各类状况下的生命周期

Android 下拉通知栏不会影响 Activity 的生命周期方法。github

弹出 Dialog,生命周期:实际上是否弹出 Dialog,并不影响 Activity 的生命周期,因此这时和正常启动时 Activity 的生命回调方法一致: onCreate() -> onStart() -> onResume()面试

Activity 的生命周期

这里咱们总结一下在实际的使用过程当中可能会遇到的一些 Acitivity 的生命周期过程:算法

  1. 当用户打开新的 Activity 或者切换回桌面:会通过的生命周期为 onPause()->onStop()。由于此时 Activity 已经变成不可见了,固然,若是新打开的 Activity 用了透明主题,那么 onStop() 不会被调用,所以原来的 Activity 只是不能交互,可是仍然可见。
  2. 重新的 Activity 回到以前的 Activity 或者从桌面回到以前的 Activity:会通过的生命周期为 onRestart()->onStart()-onResume()。此时是从 onStop() 经 onRestart() 回到 onResume() 状态。
  3. 若是在上述 1 的状况下,进入后台的 Activity 由于内存不足被销毁了,那么当再次回到该 Activity 的时候,生命周期方法将会从 onCreate() 开始执行到 onResume()。
  4. 当用户按下 Back 键时:若是当前 Activity 被销毁,那么通过的生命周期将会是 onPause()->onStop()->onDestroy()

具体地,当存在两个 Activity,分别是 A 和 B 的时候,在各类状况下,它们的生命周期将会通过:数据库

  1. Back 键 Home 键
    1. 当用户点击 A 中按钮来到 B 时,假设 B 所有遮挡住了 A,将依次执行:A.onPause()->B.onCreate()->B.onStart()->B.onResume->A.onStop()
    2. 接1,此时若是点击 Back 键,将依次执行:B.onPause()->A.onRestart()->A.onStart()->A.onResume()->B.onStop()->B.onDestroy()
    3. 接2,此时若是按下 Back 键,系统返回到桌面,并依次执行:A.onPause()->A.onStop()->A.onDestroy()
    4. 接2,此时若是按下 Home 键(非长按),系统返回到桌面,并依次执行A.onPause()->A.onStop()。因而可知,Back 键和 Home 键主要区别在因而否会执行 onDestroy()。
    5. 接2,此时若是长按 Home 键,不一样手机可能弹出不一样内容,Activity 生命周期未发生变化。
  2. 横竖屏切换时 Activity 的生命周期
    1. 不设置 Activity 的 android:configChanges 时,切屏会从新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次。
    2. 设置 Activity 的 android:configChanges=“orientation” 时,切屏仍是会从新调用各个生命周期,切横、竖屏时只会执行一次。
    3. 设置 Activity 的 android:configChanges=“orientation|keyboardHidden” 时,切屏不会从新调用各个生命周期,只会执行 onConfiguration() 方法。
  • Q:Activity 之间的通讯方式
  1. Intent + onActivityResult() + setResult()
  2. 静态变量(跨进程不行)
  3. 全局通讯,广播或者 EventBus
  • Q:AlertDialog, PopupWindow, Activity 区别

AlertDialog 是 Dialog 的子类,因此它包含了 Dialog 类的不少属性和方法。是弹出对话框的主要方式,对话框分红支持包的和非支持包的,UI 效果上略有区别。设计模式

AlertDialog 与 PopupWindow 之间最本质的差别在于缓存

  1. AlertDialog 是非阻塞式对话框;而PopupWindow 是阻塞式对话框。AlertDialog 弹出时,后台还能够作事情;PopupWindow 弹出时,程序会等待,在PopupWindow 退出前,程序一直等待,只有当咱们调用了 dismiss() 方法的后,PopupWindow 退出,程序才会向下执行。咱们在写程序的过程当中能够根据本身的须要选择使用 Popupwindow 或者是 Dialog.
  2. 二者最根本的区别在于有没有新建一个 window,PopupWindow 没有新建,而是经过 WMS 将 View 加到 DecorView;Dialog 是新建了一个 window (PhoneWindow),至关于走了一遍 Activity 中建立 window 的流程。

Activity 与 Dialog 相似,都会使用 PhoneWindow 来做为 View 的容器。Activity 也能够经过设置主题为 Dialog 的来将其做为对话框来使用。Dialog 也能够经过设置 Theme 来表现得像一个 Activity 同样做为整个页面。但 Activity 具备生命周期,而且它的生命周期归 AMS 管,而 Dialog 不具备生命周期,它归 WMS 管。

  • Q:Activity 与 Service 通讯的方式

前提是是否跨进程,若是不跨进程的话,EventBus 和 静态变量都能传递信息,不然须要 IPC 才行:

  1. Binder 用于跨进程的通讯方式,AIDL 能够用来进行与远程通讯,绑定服务的时候能够拿到远程的 Binder,而后调用它的方法就能够从远程拿数据。那么若是但愿对远程的服务进行监听呢?可使用 AIDL 中的 oneway 来定义回调的接口,而后在方法中传入回调便可。也可使用 Messenger,向远程发送信息的时候,附带本地的 Messenger,而后远程获取本地的 Messenger 而后向其发送信息便可,详见 IPC 相关一文:《Android 高级面试-2:IPC 相关》
  2. 广播:使用广播实现跨进程通讯
  3. 启动服务的时候传入值,使用 startService() 的方式

关于 Activity 相关的内容能够参考笔者的文章:《Android 基础回顾:Activity 基础》

1.2 Service

  • Q:怎么启动 Service
  • Q:Service 的开启方式
  • Q:Service 生命周期

Service的生命周期图
其余,

  1. Service 有绑定模式和非绑定模式,以及这两种模式的混合使用方式。不一样的使用方法生命周期方法也不一样。
    1. 非绑定模式:当第一次调用 startService() 的时候执行的方法依次为 onCreate()->onStartCommand();当 Service 关闭的时候调用 onDestory()
    2. 绑定模式:第一次 bindService() 的时候,执行的方法为 onCreate()->onBind();解除绑定的时候会执行 onUnbind()->onDestory()
  2. 咱们在开发的过程当中还必须注意 Service 实例只会有一个,也就是说若是当前要启动的 Service 已经存在了那么就不会再次建立该 Service 固然也不会调用 onCreate() 方法。因此,
    1. 当第一次执行 startService(intent) 的时候,会调用该 Service 中的 onCreate()onStartCommand() 方法。
    2. 当第二次执行 startService(intent) 的时候,只会调用该 Service 中的 onStartCommand() 方法。(所以已经建立了服务,因此不须要再次调用 onCreate() 方法了)。
  3. bindService() 方法的第三个参数是一个标志位,这里传入 BIND_AUTO_CREATE 表示在Activity 和 Service 创建关联后自动建立 Service,这会使得 MyService 中的 onCreate() 方法获得执行,但 onStartCommand() 方法不会执行。因此,在上面的程序中当调用了bindService() 方法的时候,会执行的方法有,Service 的 onCreate() 方法,以及 ServiceConnection 的 onServiceConnected() 方法。
  4. 在 3 中,若是想要中止 Service,须要调用 unbindService() 才行。
  5. 若是咱们既调用了 startService(),又调用 bindService() 会怎么样呢?这时无论你是单独调用 stopService() 仍是 unbindService(),Service 都不会被销毁,必需要将两个方法都调用 Service 才会被销毁。也就是说,stopService() 只会让 Service 中止,unbindService() 只会让 Service 和 Activity 解除关联,一个 Service 必需要在既没有和任何 Activity 关联又处理中止状态的时候才会被销毁。
  • 进程保活
  • App 中唤醒其余进程的实现方式

1.3 Broadcast

  • Q:BroadcastReceiver,LocalBroadcastReceiver 区别
  • Q:广播的使用场景
  • Q:广播的使用方式,场景
  • Q:广播的分类?
  • Q:广播(动态注册和静态注册区别,有序广播和标准广播)

分类

  1. 按照注册方式:静态注册和动态注册两种:
    1. 静态广播直接在 manifest 中注册。限制:
      1. 在 Android 8.0 的平台上,应用不能对大部分的广播进行静态注册,也就是说,不能在 AndroidManifest 文件对有些广播进行静态注册;
      2. 当程序运行在后台的时候,静态广播中不能启动服务。
    2. 动态广播与静态广播类似,可是不须要在 Manifest 中进行注册。注意当页面被销毁的时候须要取消注册广播!
  2. 按照做用范围:本地广播和普通广播两种,
    1. 普通广播是全局的,全部应用程序均可以接收到,容易会引发安全问题。
    2. 本地广播只可以在应用内传递,广播接收器也只能接收应用内发出的广播。本地广播的核心类是 LocalBroadcastManager,使用它的静态方法 getInstance() 获取一个单例以后就可使用该单例的 registerReceiver()unregisterReceiver()sendBroadcast() 等方法来进行操做了。
  3. 按照是否有序:有序广播和无序广播两种,无序广播各接收器接收的顺序没法肯定,而且在广播发出以后接收器只能接收,不能拦截和进行其余处理,二者的区别主要体如今发送时调用的方法上。优先级高的会先接收到,优先级相等的话则顺序不肯定。而且前面的广播能够在方法中向 Intent 写入数据,后面的广播能够接收到写入的值。

1.4 ContentProvider

  • Q:Android 系统为何会设计 ContentProvider,进程共享和线程安全问题

ContentProvider 在 Android 中的做用是对外共享数据,提供了数据访问接口,用于在不一样应用程序之间共享数据,同时还能保证被访问数据的安全性。它一般用来提供一些公共的数据,好比用来查询文件信息,制做音乐播放器的时候用来读取系统中的音乐文件的信息。

与 SQLiteDatabase 不一样,ContentProvider 中的 CRUD 不接收表名参数,而是 Uri 参数。内容 URI 是内容提供器中数据的惟一标识符,包括权限和路径。

并发访问时,不管是不一样的进程仍是同一进程的不一样线程,当使用 AMS 获取 Provider 的时候返回的都是同一实例。咱们使用 Provider 来从远程访问数据,当 query() 方法运行在不一样的线程,其实是运行在 Provider 方的进程的 Binder 线程池中。经过 Binder 的线程池来实现多进程和多线程访问的安全性。

参考:Android ContentProvider的线程安全(一)

1.5 Fragment

  • Q:Fragment 各类状况下的生命周期
  • Q:Activity 与 Fragment 之间生命周期比较

  • Q:Fragment 之间传递数据的方式?
  1. 同一 Activity 的 Fragment 之间可使用 ViewModel 来交换数据;
  2. 使用 EventBus,广播,静态的;
  3. 经过 Activity 获取到另外一个 Fragment,强转以后使用它对外提供的 public 方法进行通讯;
  4. 经过 Activity 获取到另外一个 Fragment,该 Fragment 实现某个接口,而后转成接口以后进行通讯(也适用于 Activity 与 Fragment 之间),强转以后使用它对外提供的 public 方法进行通讯;
  • Q:如何实现 Fragment 的滑动

1.6 Context

  • Q:Application 和 Activity 的 Context 对象的区别

Context 的继承关系以下所示,因此,Android 中的 Context 数量 = Activity 的数量 + Service 的数量 + 1 (Application)

Context 的继承关系

Context 的用途比较广,好比用来获取图片、字符串,显式对话框等,大部分状况下,使用哪一个 Context 均可以,少数状况下只能使用特定的 Context. 好比启动 Activity 的时候,要求传入 Activity 的 Context,由于 AMS 须要直到启动指定 Activity 的 Activity 的栈。通常状况下,能使用 Application 的 Context 尽可能使用它的,由于它的生命周期更长。

Context 之间使用的是装饰者设计模式,其中 Context 是一个抽象的类。ContextWrapper 内部实际使用 ContextImpl 实现的,所以全部的逻辑基本是在 ContextImpl 中实现的。而后对于 ContextThemeWrapper,它在 ContextWrapper 的基础之上又进行了一层装饰,就是与主题相关的东西。

新版的 Activity 启动中将 Activity 的各个回调执行的逻辑放在了各个 ClientTransactionItem 中,好比 LaunchActivityItem 表示用来启动 Activity。 最终执行逻辑的时候是调用它们的 execute() 方法并使用传入的 ClientTransactionHandler 真正执行任务。而这里的 ClientTransactionHandler 实际上就是 ActivityThread,因此它将调用到 Activity 的 handleLaunchActivity() 启动 Activity. 而后程序进入到 performLaunchActivity() 中。这个方法中会建立上面的 Application 和 Activity 对应的 Context:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // ...

        // 建立 Activity 的 Context
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            // 建立新的 Activity
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            // ...
        } catch (Exception e) {
            // ... handle exception
        }

        try {
            // 建立应用的 Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                // Activity 的配置
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                // 建立窗口
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                // 关联 Activity 和 Context
                appContext.setOuterContext(activity);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);

                // ...
                // 设置 Activity 的主题
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

                // 回调 Activity 的生命周期方法
                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                r.activity = activity;
            }
            r.setState(ON_CREATE);
            mActivities.put(r.token, r);
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            // ... handle exception
        }

        return activity;
    }

    public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
        // ...

        try {
            // 建立 Application 的 Context
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            // 关联 Application 和 Context
            appContext.setOuterContext(app);
        } catch (Exception e) {
            // ... handle exception
        }
        // ...
        return app;
    }
复制代码

1.7 其余

  • Q:AndroidManifest 的做用与理解

声明了四大组件、应用版本、权限等的配置信息,会在解析 APK 的时候由 PMS 进行解析,而后解析的结果会被缓存到 PMS 中。

二、Android API

2.1 AsyncTask

  • Q:AsyncTask 机制,如何取消 AsyncTask
  • Q:多线程(关于 AsyncTask 缺陷引起的思考)
  • Q:Asynctask 有什么优缺点
  • Q:AsyncTask 机制、原理及不足?

AsyncTask 是 Android 提供的用来执行异步操做的 API,咱们能够经过它来执行异步操做,并在获得结果以后将结果放在主线程当中进行后续处理。

AsyncTask 的缺点是在使用多个异步操做和并须要进行 Ui 变动时,就变得复杂起来(会致使多个 AsyncTask 进行嵌套)。若是有多个地方须要用到 AsyncTask,可能须要定义多个 AsyncTask 的实现。

若是 AsyncTask 以一个非静态的内部类的形式声明在 Activity 中,那么它会持有 Activity 的匿名引用,若是销毁 Activity 时 AsyncTask 还在执行异步任务的话,Activity 就不能销毁,会形成内存泄漏。解决方式是,要么将 AsyncTask 定义成静态内部类,要么在 Activity 销毁的时候调用 cancel() 方法取消 AsyncTask.在屏幕旋转或 Activity 意外结束时,Activity 被建立,而 AsyncTask 会拥有以前 Activity 的引用,会致使结果丢失。

AsyncTask 在 1.6 以前是串行的,1.6 以后是并行的,3.0 以后又改为了串行的。不过咱们能够经过调用 executeOnExecutor() 方法并传入一个线程池,来让 AsyncTask 在某个线程池中并行执行任务。

AsyncTask 的源码就是将一个任务封装成 Runnable 以后放进线程池当中执行,执行完毕以后调用主线程的 Handler 发送消息到主线程当中进行处理。任务在默认线程池当中执行的时候,会被加入到一个双端队列中执行,执行完一个以后再执行下一个,以此来实现任务的串行执行。

了解 AsyncTask 的源码,能够参考笔者的这篇文章:《Android AsyncTask 源码分析》

  • Q:介绍下 SurfaceView

SurfaceView 以及 TextureView 均继承于 android.view.View,它们都在独立的线程中绘制和渲染,常被用在对绘制的速率要求比较高的应用场景中,用来解决普通 View 由于绘制的时间延迟而带来的掉帧的问题,好比用做相机预览、视频播放的媒介等。

SurfaceView 提供了嵌入在视图层次结构内的专用绘图图层 (Surface)。图层 (Surface) 处于 Z 轴,位于持有 SurfaceView 的窗口以后。SurfaceView 在窗口上开了一个透明的 “洞” 以展现图面。Surface 的排版显示受到视图层级关系的影响,它的兄弟视图结点会在顶端显示。注意,若是 Surface 上面有透明控件,那么每次 Surface 变化都会引发框架从新计算它和顶层控件的透明效果,这会影响性能。SurfaceView 使用双缓冲,SurfaceView 自带一个 Surface,这个 Surface 在 WMS 中有本身对应的WindowState,在 SurfaceFlinger 中也会有本身的 Layer。这样的好处是对这个Surface的渲染能够放到单独线程去作。由于这个 Surface 不在 View hierachy 中,它的显示也不受 View 的属性控制,因此不能进行平移,缩放等变换,也不能放在其它 ViewGroup 中,一些 View 中的特性也没法使用。

TextureView 在 Andriod 4.0 以后的 API 中才能使用,而且必须在硬件加速的窗口中。和 SurfaceView 不一样,它不会在 WMS 中单首创建窗口,而是做为 View hierachy 中的一个普通 View,所以能够和其它普通 View 同样进行移动,旋转,缩放,动画等变化。它占用内存比 SurfaceView 高,在 5.0 之前在主线程渲染,5.0 之后有单独的渲染线程。

更多内容请参考:Android:解析 SurfaceView & TextureView

2.2 View 体系

事件分发机制等

  • Q:Android 事件分发机制
  • Q:事件传递机制的介绍
  • Q:View 事件传递
  • Q:触摸事件的分发?

Activity 的层级:Activity->PhoneWindow->DecorView

当触摸事件发生的时候,首先会被 Activity 接收到,而后该 Activity 会经过其内部的 dispatchTouchEvent(MotionEvent) 将事件传递给内部的 PhoneWindow;接着 PhoneWindow 会把事件交给 DecorView,再由 DecorView 交给根 ViewGroup。剩下的事件传递就只在 ViewGroupView 之间进行。

事件分发机制本质上是一个深度优先的遍历算法。事件分发机制的核心代码:

boolean dispatchTouchEvent(MotionEvent e) {
        boolean result;
        if (onInterceptTouchEvent(e)) { // 父控件能够覆写并返回 true 以拦截
            result = super.dispatchTouchEvent(e); // 调用 View 中该方法的实现
        } else {
            for (child in children) {
                result = child.dispatchTouchEvent(e); // 这里的 child 分红 View 和 ViewGroup 二者情形
                if (result) break; // 被子控件消费,中止分发
            }
        }
        return result;
    }
复制代码

对于 dispatchTouchEvent() 方法,在 View 的默认实现中,会先交给 onTouchEvent() 进行处理,若它返回了 true 就消费了,不然根据触摸的类型,决定是交给 OnClickListener 仍是 OnLongClickListener 继续处理。

事件分发机制和 View 的体系请参考笔者文章:《View 体系详解:坐标系、滑动、手势和事件分发机制》,总体上事件分发机制应该分红三个阶段来进行说明:1).从 Activity 到 DecorView 的过程;2).ViewGroup 中的分发的过程;3).交给 View 以后的实现过程。

  • Q:封装 View 的时候怎么知道 View 的大小
  • Q:点击事件被拦截,可是想传到下面的 View,如何操做?
  • Q:计算一个 view 的嵌套层级

按照广度优先算法进行遍历

2.3 列表控件

  • Q:ListView 的优化?
  • Q:ListView 重用的是什么?

ListView 默认缓存一页的 View,也就是你当前 Listview 界面上有几个 Item 能够显示,,Lstview 就缓存几个。当现实第一页的时候,因为没有一个 Item 被建立,因此第一页的 Item 的 getView() 方法中的第二个参数都是为 null 的。

ViewHolder 一样也是为了提升性能。就是用来在缓存使用 findViewById() 方法获得的控件,下次的时候能够直接使用它而不用再进行 find 了。

关于 ListView 的 ViewHolder 等的使用,能够参考这篇文章:ListView 复用和优化详解

  • Q:RecycleView 的使用,原理,RecycleView 优化
  • Q:Recycleview Listview 的区别,性能
  1. 装饰;
  2. 手势滑动、拖拽;
  3. 顶部悬浮效果;
  • Q:Listview 图片加载错乱的原理和解决方案

2.4 其余控件

  • Q:LinearLayout、RelativeLayout、FrameLayout 的特性、使用场景
  • Q:ViewPager 使用细节,如何设置成每次只初始化当前的 Fragment,其余的不初始化

2.5 数据存储

  • Q:Android 中数据存储方式

SP,SQLite,ContentProvider,File,Server

三、架构相关

  • Q:模块化实现(好处,缘由)
  • Q:项目组件化的理解
  • Q:模式 MVP、MVC 介绍
  • Q:MVP 模式

MVC (Model-View-Controller, 模型-视图-控制器),标准的MVC是这个样子的:

  1. 模型层 (Model):业务逻辑对应的数据模型,无 View 无关,而与业务相关;
  2. 视图层 (View):通常使用 XML 或者 Java 对界面进行描述;
  3. 控制层 (Controllor):在 Android 中一般指 Activity 和Fragment,或者由其控制的业务类。

在 Android 开发中,就是指直接使用 Activity 并在其中写业务逻辑的开发方式。显然,一方面 Activity 自己就是一个视图,另外一方面又要负责处理业务逻辑,所以逻辑会比较混乱。这种开发方式不太适合 Android 开发。

MVP (Model-View-Presenter)

  1. 模型层 (Model):主要提供数据存取功能。
  2. 视图层 (View):处理用户事件和视图。在 Android 中,多是指 Activity、Fragment 或者 View。
  3. 展现层 (Presenter):负责经过 Model 存取书数据,链接 View 和 Model,从 Model 中取出数据交给 View。

实际开发中会像下面这样定义一个契约接口

public interface HomeContract {

        interface IView extends BaseView {
            void setFirstPage(List<HomeBean.IssueList.ItemList> itemLists);
            void setNextPage(List<HomeBean.IssueList.ItemList> itemLists);
            void onError(String msg);
        }

        interface IPresenter extends BasePresenter {
            void requestFirstPage();
            void requestNextPage();
        }
    }
复制代码

而后让 Fragment 或者 Activity 等继承 IView,实例化一个 IPresenter,并在构造方法中将本身引入到 IPresenter 中。这样 View 和 Presenter 就相互持有了对方的引用。当要发起一个网络请求的时候,View 中调用 Presenter 的方法,Presenter 拿到告终果以后回调 View 的方法。这样就使得 View 和 Presenter 只须要关注自身的责任便可。

MVP 缺点:1). Presenter 中除了应用逻辑之外,还有大量的 View->Model,Model->View 的手动同步逻辑,形成 Presenter 比较笨重,维护起来会比较困难;2). 因为对视图的渲染放在了 Presenter 中,因此视图和 Presenter 的交互会过于频繁;3). 若是 Presenter 过多地渲染了视图,每每会使得它与特定的视图的联系过于紧密,一旦视图须要变动,那么 Presenter 也须要变动了。

MVVM 是 Model-View-ViewModel 的简写。它本质上就是 MVC 的改进版。MVVM 就是将其中的 View 的状态和行为抽象化,让咱们将视图 UI 和业务逻辑分开。

  1. 模型层 (Model):负责从各类数据源中获取数据;
  2. 视图层 (View):在 Android 中对应于 Activity 和 Fragment,用于展现给用户和处理用户交互,会驱动 ViewModel 从 Model 中获取数据;
  3. ViewModel 层:用于将 Model 和 View 进行关联,咱们能够在 View 中经过 ViewModel 从 Model 中获取数据;当获取到了数据以后,会经过自动绑定,好比 DataBinding,来将结果自动刷新到界面上。

优势:

  1. 低耦合:视图(View)能够独立于Model变化和修改,一个 ViewModel 能够绑定到不一样的 View 上,当 View 变化的时候 Model 能够不变,当 Model 变化的时候 View 也能够不变。
  2. 可重用性:你能够把一些视图逻辑放在一个 ViewModel 里面,让不少 view 重用这段视图逻辑。
  3. 独立开发:开发人员能够专一于业务逻辑和数据的开发(ViewModel),设计人员能够专一于页面设计。
  4. 可测试:界面素来是比较难于测试的,而如今测试能够针对 ViewModel 来写。

关于移动应用架构部份内容能够参考笔者的文章:《Android 架构设计:MVC、MVP、MVVM和组件化》,另外关于 MVVM 架构设计中的 ViewModel 和 LiveData 的机制能够参考:《浅谈 ViewModel 的生命周期控制》《浅谈 LiveData 的通知机制》

四、系统源码

  • Q:画出 Android 的大致架构图
  • Q:App 启动流程,从点击桌面开始
  • Q:Activity 栈
  • Q:简述 Activity 启动所有过程?
  • Q:ActicityThread 相关?

startActivity() -> Process.start() -> Socket -> (SystemServer) -> Zygote.fork() -> VM,Binder 线程池 -> ActivityThread.main()

Instrumentation.execStartActivity() -> IApplicationThread+AMS+H -> 校验用户信息等 -> 解析 Intent -> 回调 IApplicationThread.scheduleTransaction()

  • Q:App 是如何沙箱化,为何要这么作?

Android是一个权限分离的系统,这是利用 Linux 已有的权限管理机制,经过为每个 Application 分配不一样的 uid 和 gid,从而使得不一样的 Application 之间的私有数据和访问(native以及java层经过这种 sandbox 机制,均可以)达到隔离的目的 。与此同时,Android 还在此基础上进行扩展,提供了 permission 机制,它主要是用来对 Application 能够执行的某些具体操做进行权限细分和访问控制,同时提供了 per-URI permission 机制,用来提供对某些特定的数据块进行 ad-hoc 方式的访问。

  • Q:权限管理系统(底层的权限是如何进行 grant 的)

  • Q:动态权限适配方案,权限组的概念

  • Q:大致说清一个应用程序安装到手机上时发生了什么?

  • Q:应用安装过程

应用安装的时候涉及几个类,分别时 PackageManager, ApplicationPackageManager 和 PMS. 它们之间的关系是,PackageManager 是一个抽象类,它的具体实现是 ApplicationPackageManager,然后者的全部实现都是靠 PMS 实现的。PMS 是一种远程的服务,与 AMS 类似,在同一方法中启动。另外,还有 Installer 它是安装应用程序的辅助类,它也是一种系统服务,与 PMS 在同一方法中启动。它会经过 Socket 与远程的 Installd 创建联系。这是由于权限的问题,PMS 只有 system 权限。installd 倒是具备 root 权限。(Installd 的做用好像就是建立一个目录)

installd是由Android系统init进程(pid=1),在解析init.rc文件的代码时,经过fork建立用户空间的守护进程intalld。启动时,进入监听socket,当客户端发送过来请求时,接收客户端的请求,并读取客户端发送过来的命令数据,并根据读取客户端命令来执行命令操做。

Android上应用安装能够分为如下几种方式:

  1. 系统安装:开机的时候,没有安装界面
  2. adb 命令安装:经过abd命令行安装,没有安装界面
  3. 应用市场安装,这个要视应用的权限,有系统的权限无安装界面(例如MUI的小米应用商店)
  4. 第三方安装,有安装界面,经过packageinstaller.apk来处理安装及卸载的过程的界面

apk的大致流程以下:

  1. 第一步:拷贝文件到指定的目录:在Android系统中,apk安装文件是会被保存起来的,默认状况下,用户安装的apk首先会被拷贝到/data/app目录下,/data/app目录是用户有权限访问的目录,在安装apk的时候会自动选择该目录存放用户安装的文件,而系统出场的apk文件则被放到了/system分区下,包括/system/app,/system/vendor/app,以及/system/priv-app等等,该分区只有ROOT权限的用户才能访问,这也就是为何在没有Root手机以前,咱们无法删除系统出场的app的缘由了。
  2. 第二步:解压缩apk,宝贝文件,建立应用的数据目录:为了加快app的启动速度,apk在安装的时候,会首先将app的可执行文件dex拷贝到/data/dalvik-cache目录,缓存起来。而后,在/data/data/目录下建立应用程序的数据目录(以应用的包名命名),存放在应用的相关数据,如数据库、xml文件、cache、二进制的so动态库等。
  3. 第三步:解析apk的AndroidManifest.xml文件:提取出这个apk的重要信息写入到packages.xml文件中,这些信息包括:权限、应用包名、APK的安装位置、版本、userID等等。
  4. 第四步:显示快捷方式:Home应用程序,负责从PackageManagerService服务中把这些安装好的应用程序取出来。在Android系统中,负责把系统中已经安装的应用程序在桌面中展示出来的Home应用就是Launcher了。

普通安装:

PackagInstaller是安卓上默认的应用程序,用它来安装普通文件。PackageInstaller调用一个叫作InstallAppProgress的activity来获取用户发出的指令。InstallAppProgress会请求Package Manager服务,而后经过installed来安装包文件。它提供了安装的页面和安装进度相关页面,咱们平时安装应用时显式的就是它。(源码

最终的安卓过程则是交给 PMS 的 installPackageLI() 方法来完成,它也会先对 manifest 进行解析,而后将解析的结果添加到 PMS 的缓存中,并注册四大组件。

若是是第一次安装的时候就会调用 scanPackageLI() 方法来进行安装。

安装大体流程图

  1. PMS 在启动 SystemSever 时启动,调用构造方法,对目录进行扫描,包括系统、供应商等的目录,复制 APK,解析 APK,缓存 APK 信息;
  2. ADB -> pm.jar -> PMS -> Installd() -> Installer(系统服务)
  • Q:系统启动流程 Zygote 进程 –> SystemServer 进程 –> 各类系统服务 –> 应用进程?

按下电源以后,首先加载引导程序 BootLoader 到 RAM;而后,执行引导程序 BootLoader 以把系统 OS 拉起来;接着,启动 Linux 内核;内核中启动的第一个用户进程是 init 进程,init 进程会经过解析 init.rc 来启动 zygote 服务;Zygote 又会进一步的启动 SystemServer;在 SystemServer 中,Android 会启动一系列的系统服务供用户调用。

Init 进程会启动以后会解析 init.rc 文件,该文件由固定格式的指令组成,在 AOSP 中有说明它的规则。其中的每一个指令对应系统中的类。在解析文件的时候会将其转换成对应的类的实例来保存,好比 service 开头的指令会被解析成 Service 对象。在解析 init.rc 文件以前会根据启动时的属性设置加载指定的 rc 文件到系统中。当解析完毕以后系统会触发初始化操做。它会经过调用 Service 的 start() 方法来启动属性服务,而后进入 app_main.cpp 的 main() 方法。这个方法中会根据 service 指令的参数来决定调用 Java 层的 ZygoteInit 仍是 RuntimeInit. 这里会调用后者的 ZygoteInit 初始化 Zygote 进程。

if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        app_usage();
    }
复制代码

它调用的方式是经过 runtime 的 start() 方法,而这个 runtime 就是 AndroidRuntime. 可是在执行 Java 类以前得先有虚拟机,因此它会先启动虚拟机实例,而后再调用 ZygoteInit 的方法。因此,AndroidRuntime 的 start() 方法中主要作了三件事情:1).调用函数 startVM() 启动虚拟机;2).调用函数 startReg() 注册 JNI 方法;3).调用 com.android.internal.os.ZygoteInit 类的 main() 函数。

ZygoteInit 用来初始化 Zygote 进程的,它的 main() 函数中主要作了三件事情:

  1. 调用 registerZygoteSocket() 函数建立了一个 socket 接口,用来和 AMS 通信;(Android 应用程序进程启动过程当中,AMS 是经过 Process.start() 函数来建立一个新的进程的,而 Process.start() 函数会首先经过 Socket 链接到 Zygote 进程中,最终由 Zygote 进程来完成建立新的应用程序进程,而 Process 类是经过 openZygoteSocketIfNeeded() 函数来链接到 Zygote 进程中的 Socket.)
  2. 调用 startSystemServer() 函数来启动 SystemServer 组件,Zygote 进程经过 Zygote.forkSystemServer() 函数来建立一个新的进程来启动 SystemServer 组件;(SystemServer 的 main 方法将会被调用,并在这里启动 Binder 中的 ServiceManager 和各类系统运行所需的服务,PMS 和 AMS 等)
  3. 调用 runSelectLoopMode() 函数进入一个无限循环在前面建立的 socket 接口上等待 AMS 请求建立新的应用程序进程。

总结一下:

  1. 系统启动时 init 进程会建立 Zygote 进程,Zygote 进程负责后续 Android 应用程序框架层的其它进程的建立和启动工做。
  2. Zygote 进程会首先建立一个 SystemServer 进程,SystemServer 进程负责启动系统的关键服务,如包管理服务 PMS 和应用程序组件管理服务 AMS。
  3. 当咱们须要启动一个 Android 应用程序时,AMS 会经过 Socket 进程间通讯机制,通知 Zygote 进程为这个应用程序建立一个新的进程。
  • Q:描述清点击 Android Studio 的 build 按钮后发生了什么?

编译打包的过程->adb->安装过程 PMS->应用启动过程 AMS


Android 高级面试系列文章,关注做者及时获取更多面试资料

本系列以及其余系列的文章均维护在 Github 上面:Github / Android-notes,欢迎 Star & Fork. 若是你喜欢这篇文章,愿意支持做者的工做,请为这篇文章点个赞👍!

相关文章
相关标签/搜索