Android 面试之必问Android基础

在上一篇文章Android 面试之必问Java基础一文中,咱们介绍了Java面试的一些常见的基础面试题,下面咱们来介绍Android开发的一些必问知识点。java

1,Activity

1.1 生命周期

正常状况系,Activity会经历以下几个阶段:android

  • onCreate:表示Activity正在被建立。
  • onRestart:表示Activity正在被从新启动。
  • onStart:表示Activity正在被启动,这时已经可见,但没有出如今前台没法进行交互。
  • onResume:表示Activity已经可见,而且处于前台。
  • onPause:表示Activity正在中止(可作一次保存状态中止动画等非耗时操做)。
  • onStop:表示Activity即将中止(可进行重量级回收工做)。
  • onDestroy:表示Activity即将被销毁。

在这里插入图片描述
对于生命周期,一般还会问以下的一些问题:git

  • 第一次启动:onCreate->onStart->onResume;
  • 打开新的Activity或者返回桌面:onPause->onStop。若是打开新的Activity为透明主题,则不会调用onStop;
  • 当回到原来Activity时:onRestart->onStart->onResume;
  • 当按下返回键:onPause->onStop->onDestroy

1.2 启动模式

Activity的启动模式有四种:Standard、SingleTop、SingleTask和SingleInstance。github

  • Standard:标准模式,也是默认模式。每次启动都会建立一个全新的实例。
  • SingleTop:栈顶复用模式。这种模式下若是Activity位于栈顶,不会新建实例。onNewIntent会被调用,接收新的请求信息,不会再低啊用onCreate和onStart。
  • SingleTask:栈内复用模式。升级版singleTop,若是栈内有实例,则复用,并会将该实例之上的Activity所有清除。
  • SingleInstance:系统会为它建立一个单独的任务栈,而且这个实例独立运行在一个 task中,这个task只有这个实例,不容许有别的Activity 存在(能够理解为手机内只有一个)。

1.3 启动流程

在理解Activity的启动流程以前,先让咱们来看一下Android系统启动流程。总的来讲,Android系统启动流程的主要经历init进程 -> Zygote进程 –> SystemServer进程 –> 各类系统服务 –> 应用进程等阶段。面试

  1. 启动电源以及系统启动:当电源按下时引导芯片从预约义的地方(固化在ROM)开始执行,加载引导程序BootLoader到RAM,而后执行。
  2. 引导程序BootLoader:BootLoader是在Android系统开始运行前的一个小程序,主要用于把系统OS拉起来并运行。
  3. Linux内核启动:当内核启动时,设置缓存、被保护存储器、计划列表、加载驱动。当其完成系统设置时,会先在系统文件中寻找init.rc文件,并启动init进程。
  4. init进程启动:初始化和启动属性服务,而且启动Zygote进程。
  5. Zygote进程启动:建立JVM并为其注册JNI方法,建立服务器端Socket,启动SystemServer进程。
  6. SystemServer进程启动:启动Binder线程池和SystemServiceManager,而且启动各类系统服务。
  7. Launcher启动:被SystemServer进程启动的AMS会启动Launcher,Launcher启动后会将已安装应用的快捷图标显示到系统桌面上。

参考:
Android系统启动流程之init进程启动
Android系统启动流程之Zygote进程启动
Android系统启动流程之SystemServer进程启动
Android系统启动流程之Launcher进程启动json

Launcher进程启动后,就会调用Activity的启动了。首先,Launcher会调用ActivityTaskManagerService,而后ActivityTaskManagerService会调用ApplicationThread,而后ApplicationThread再经过ActivityThread启动Activity,完整的分析能够参考Android 之 Activity启动流程canvas

2,Fragment

2.1 简介

Fragment,是Android 3.0(API 11)提出的,为了兼容低版本,support-v4库中也开发了一套Fragment API,最低兼容Android 1.6,若是要在最新的版本中使用Fragment,须要引入AndroidX的包。小程序

相比Activity,Fragment具备以下一些特色:segmentfault

  • 模块化(Modularity):咱们没必要把全部代码所有写在Activity中,而是把代码写在各自的Fragment中。
  • 可重用(Reusability):多个Activity能够重用一个Fragment。
  • 可适配(Adaptability):根据硬件的屏幕尺寸、屏幕方向,可以方便地实现不一样的布局,这样用户体验更好。

Fragment有以下几个核心的类:数组

  • Fragment:Fragment的基类,任何建立的Fragment都须要继承该类。
  • FragmentManager:管理和维护Fragment。他是抽象类,具体的实现类是FragmentManagerImpl。
  • FragmentTransaction:对Fragment的添加、删除等操做都须要经过事务方式进行。他是抽象类,具体的实现类是BackStackRecord。

2.2 生命周期

Fragment必须是依存于Activity而存在的,所以Activity的生命周期会直接影响到Fragment的生命周期。相比Activity的生命周期,Fragment的生命周期以下所示。

  • onAttach():Fragment和Activity相关联时调用。若是不是必定要使用具体的宿主 Activity 对象的话,可使用这个方法或者getContext()获取 Context 对象,用于解决Context上下文引用的问题。同时还能够在此方法中能够经过getArguments()获取到须要在Fragment建立时须要的参数。
  • onCreate():Fragment被建立时调用。
  • onCreateView():建立Fragment的布局。
  • onActivityCreated():当Activity完成onCreate()时调用。
  • onStart():当Fragment可见时调用。
  • onResume():当Fragment可见且可交互时调用。
  • onPause():当Fragment不可交互但可见时调用。
  • onStop():当Fragment不可见时调用。
  • onDestroyView():当Fragment的UI从视图结构中移除时调用。
  • onDestroy():销毁Fragment时调用。
  • onDetach():当Fragment和Activity解除关联时调用。

以下图所示。
在这里插入图片描述
下面是Activity的生命周期和Fragment的各个生命周期方法的对应关系。
在这里插入图片描述

2.3 与Activity传递数据

2.3.1 Fragment向Activity传递数据

首先,在Fragment中定义接口,并让Activity实现该接口,以下所示。

public interface OnFragmentInteractionListener {
    void onItemClick(String str);  
}

而后,在Fragment的onAttach()中,将参数Context强转为OnFragmentInteractionListener对象传递过去。

public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentInteractionListener) {
        mListener = (OnFragmentInteractionListener) context;
    } else {
        throw new RuntimeException(context.toString()
                + " must implement OnFragmentInteractionListener");
    }
}

2.3.2 Activity向Fragment传递数据

在建立Fragment的时候,能够经过setArguments(Bundle bundle)方式将值传递给Activity,以下所示。

public static Fragment newInstance(String str) {
        FragmentTest fragment = new FragmentTest();
        Bundle bundle = new Bundle();
        bundle.putString(ARG_PARAM, str);
        fragment.setArguments(bundle);//设置参数
        return fragment;
    }

3, Service

3.1 启动方式

Service的启动方式主要有两种,分别是startService和bindService。

其中,StartService使用的是同一个Service,所以onStart()会执行屡次,onCreate()只执行一次,onStartCommand()也会执行屡次。使用bindService启动时,onCreate()与onBind()都只会调用一次。

使用startService启动时是单独开一个服务,与Activity没有任何关系,而bindService方式启动时,Service会和Activity进行绑定,当对应的activity销毁时,对应的Service也会销毁。

3.2 生命周期

下图是startService和bindService两种方式启动Service的示意图。
在这里插入图片描述

3.2.1 startService

  • onCreate():若是service没被建立过,调用startService()后会执行onCreate()回调;若是service已处于运行中,调用startService()不会执行onCreate()方法。
  • onStartCommand():屡次执行了Context的startService()方法,那么Service的onStartCommand()方法也会相应的屡次调用。
  • onBind():Service中的onBind()方法是抽象方法,Service类自己就是抽象类,因此onBind()方法是必须重写的,即便咱们用不到。
    onDestory():在销毁Service的时候该方法。
public class TestOneService extends Service{

    @Override
    public void onCreate() {
        Log.i("Kathy","onCreate - Thread ID = " + Thread.currentThread().getId());
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("Kathy", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().getId());
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i("Kathy", "onBind - Thread ID = " + Thread.currentThread().getId());
        return null;
    }

    @Override
    public void onDestroy() {
        Log.i("Kathy", "onDestroy - Thread ID = " + Thread.currentThread().getId());
        super.onDestroy();
    }
}

3.2.2 bindService

bindService启动的服务和调用者之间是典型的Client-Server模式。调用者是client,Service则是Server端。Service只有一个,但绑定到Service上面的Client能够有一个或不少个。bindService启动服务的生命周期与其绑定的client息息相关。

1,首先,在Service的onBind()方法中返回IBinder类型的实例。
2,onBInd()方法返回的IBinder的实例须要可以返回Service实例自己。

3.3 Service不被杀死

如今,因为系统API的限制,一些常见的不被杀死Service方式已通过时,好比下面是以前的一些方式。

3.3.1, onStartCommand方式中,返回START_STICKY。

调用Context.startService方式启动Service时,若是Android面临内存匮乏,可能会销毁当前运行的Service,待内存充足时能够重建Service。而Service被Android系统强制销毁并再次重建的行为依赖于Service的onStartCommand()方法的返回值,常见的返回值有以下一些。

START_NOT_STICKY:若是返回START_NOT_STICKY,表示当Service运行的进程被Android系统强制杀掉以后,不会从新建立该Service。
START_STICKY:若是返回START_STICKY,表示Service运行的进程被Android系统强制杀掉以后,Android系统会将该Service依然设置为started状态(即运行状态),可是再也不保存onStartCommand方法传入的intent对象,即获取不到intent的相关信息。
START_REDELIVER_INTENT:若是返回START_REDELIVER_INTENT,表示Service运行的进程被Android系统强制杀掉以后,与返回START_STICKY的状况相似,Android系统会将再次从新建立该Service,并执行onStartCommand回调方法,可是不一样的是,Android系统会再次将Service在被杀掉以前最后一次传入onStartCommand方法中的Intent再次保留下来并再次传入到从新建立后的Service的onStartCommand方法中,这样咱们就能读取到intent参数。

4, BroadcastReceiver

4.1 BroadcastReceiver是什么

BroadcastReceiver,广播接收者,它是一个系统全局的监听器,用于监听系统全局的Broadcast消息,因此它能够很方便的进行系统组件之间的通讯。BroadcastReceiver属于系统级的监听器,它拥有本身的进程,只要存在与之匹配的Broadcast被以Intent的形式发送出来,BroadcastReceiver就会被激活。

和其余的四大组件同样,BroadcastReceiver也有本身独立的声明周期,可是它又和Activity、Service不一样。当在系统注册一个BroadcastReceiver以后,每次系统以一个Intent的形式发布Broadcast的时候,系统都会建立与之对应的BroadcastReceiver广播接收者实例,并自动触发它的onReceive()方法,当onReceive()方法被执行完成以后,BroadcastReceiver的实例就会被销毁。

从不一样的纬度区分,BroadcastReceiver能够分为不一样的类别。

  • 系统广播/非系统广播
  • 全局广播/本地广播
  • 无序广播/有序广播/粘性广播

4.2 基本使用

4.2.1 注册广播

广播的注册分为静态注册和动态注册。静态注册是在Mainfest清单文件中进行注册,好比。

<receiver android:name=".MyBroadcastReceiver"  android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
    </intent-filter>
</receiver>

动态注册是在代码中,使用registerReceiver方法代码进行注册,好比。

val br: BroadcastReceiver = MyBroadcastReceiver()
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
    addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
}
registerReceiver(br, filter)

4.2.2 发送广播

而后,咱们使用sendBroadcast方法发送广播。

Intent().also { intent ->
    intent.setAction("com.example.broadcast.MY_NOTIFICATION")
    intent.putExtra("data", "Notice me senpai!")
    sendBroadcast(intent)
}

4.2.3 接收广播

发送广播的时候,咱们会添加一个发送的标识,那么接收的时候使用这个标识接收便可。接收广播须要继承BroadcastReceiver,并重写onReceive回调方法接收广播数据。

private const val TAG = "MyBroadcastReceiver"

class MyBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        StringBuilder().apply {
            append("Action: ${intent.action}\n")
            append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
            toString().also { log ->
                Log.d(TAG, log)
                Toast.makeText(context, log, Toast.LENGTH_LONG).show()
            }
        }
    }
}

5, ContentProvider

ContentProvider是Android四大组件之一,不过平时使用的机会比较少。若是你看过它的底层源码,那么就应该知道ContentProvider是经过Binder进行数据共享。所以,若是咱们须要对第三方应用提供数据,能够考虑使用ContentProvider实现。

6,Android View知识点

Android自己的View体系很是庞大的,若是要彻底弄懂View的原理是很困难的,咱们这里捡一些比较重要的概念来给你们讲解。

6.1 测量流程

Android View自己的绘制流程须要通过measure测量、layout布局、draw绘制三个过程,最终才可以将其绘制出来并展现在用户面前。

首先,咱们看一下Android的MeasureSpec,Android的MeasureSpec分为3中模式,分别是EXACTLY、AT_MOST 和 UNSPECIFIED,含义以下。

  • MeasureSpec.EXACTLY:精确模式,在这种模式下,尺寸的值是多少组件的长或宽就是多少。
  • MeasureSpec.AT_MOST:最大模式,由父组件可以给出的最大的空间决定。
  • MeasureSpec.UNSPECIFIED:未指定模式,当前组件能够随便使用空间,不受限制。

相关文章:谈谈对 MeasureSpec 的理解

6.2 事件分发

Android的事件分发由dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三个方法构成。

  • dispatchTouchEvent:方法返回值为true,表示事件被当前视图消费掉;返回为super.dispatchTouchEvent表示继续分发该事件,返回为false表示交给父类的onTouchEvent处理。
  • onInterceptTouchEvent:方法返回值为true,表示拦截这个事件并交由自身的onTouchEvent方法进行消费;返回false表示不拦截,须要继续传递给子视图。若是return super.onInterceptTouchEvent(ev), 事件拦截分两种状况:即一种是有子View的状况,另外一种是没有子View的状况。
    若是该View存在子View且点击到了该子View,则不拦截,继续分发
    给子View 处理,此时至关于return false。若是该View没有子View或者有子View可是没有点击中子View,则交由该View的onTouchEvent响应,此时至关于return true。
  • onTouchEvent:方法返回值为true表示当前视图能够处理对应的事件;返回值为false表示当前视图不处理这个事件,它会被传递给父视图的onTouchEvent方法进行处理。若是return super.onTouchEvent(ev),事件处理分为两种状况,即本身消费仍是仍是向上传递。

在Android系统中,拥有事件传递处理能力的类有如下三种:

  • Activity:拥有分发和消费两个方法。
  • ViewGroup:拥有分发、拦截和消费三个方法。
  • View:拥有分发、消费两个方法。

在事件分发中,有时候会问:ACTION_CANCEL何时触发,触摸button而后滑动到外部抬起会触发点击事件吗,再滑动回去抬起会么?

对于这个问题,咱们须要明白如下内容:

  • 通常ACTION_CANCEL和ACTION_UP都做为View一段事件处理的结束。若是在父View中拦截ACTION_UP或ACTION_MOVE,在第一次父视图拦截消息的瞬间,父视图指定子视图不接受后续消息了,同时子视图会收到ACTION_CANCEL事件。
  • 若是触摸某个控件,可是又不是在这个控件的区域上抬起,也会出现ACTION_CANCEL。

在这里插入图片描述
在这里插入图片描述

  • ViewGroup 默认不拦截任何事件。ViewGroup 的 onInterceptTouchEvent 方法默认返回 false。
  • View 没有 onInterceptTouchEvent 方法,一旦有点击事件传递给它,onTouchEvent 方法就会被调用。
  • View 在可点击状态下,onTouchEvent 默认会消耗事件。
  • ACTION_DOWN 被拦截了,onInterceptTouchEvent 方法执行一次后,就会留下记号(mFirstTouchTarget == null)那么日后的 ACTION_MOVE 和 ACTION_UP 都会拦截。`

6.3 MotionEvent

Android的MotionEvent事件主要有如下几个:

  • ACTION_DOWN 手指刚接触到屏幕
  • ACTION_MOVE 手指在屏幕上移动
  • ACTION_UP 手机从屏幕上松开的一瞬间
  • ACTION_CANCEL 触摸事件取消

下面是事件的举例:点击屏幕后松开,事件序列为 DOWN -> UP,点击屏幕滑动松开,事件序列为 DOWN -> MOVE -> ...> MOVE -> UP。同时,getX/getY 返回相对于当前View左上角的坐标,getRawX/getRawY 返回相对于屏幕左上角的坐标。TouchSlop是系统所能识别出的被认为滑动的最小距离,不一样设备值可能不相同,可经过ViewConfiguration.get(getContext()).getScaledTouchSlop() 获取。

6.4 Activity、Window、DecorView之间关系

首先,来看一下Activity中setContentView的源代码。

public void setContentView(@LayoutRes int layoutResID) {
        //将xml布局传递到Window当中
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

能够看到, Activity的 setContentView实质是将 View传递到 Window的 setContentView()方法中, Window的 setContenView会在内部调用 installDecor()方法建立 DecorView,代码以下。

public void setContentView(int layoutResID) { 
        if (mContentParent == null) {
            //初始化DecorView以及其内部的content
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ...............
        } else {
            //将contentView加载到DecorVoew当中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...............
    }
  private void installDecor() {
        ...............
        if (mDecor == null) {
            //实例化DecorView
            mDecor = generateDecor(-1);
            ...............
            }
        } else {
            mDecor.setWindow(this);
        }
       if (mContentParent == null) {
            //获取Content
            mContentParent = generateLayout(mDecor);
       }  
        ...............
 }
 protected DecorView generateDecor(int featureId) {
        ...............
        return new DecorView(context, featureId, this, getAttributes());
 }

经过 generateDecor()的new一个 DecorView,而后调用 generateLayout()获取 DecorView中 content,最终经过 inflate将 Activity视图添加到 DecorView中的 content中,但此时 DecorView还未被添加到 Window中。添加操做须要借助 ViewRootImpl。

ViewRootImpl的做用是用来衔接 WindowManager和 DecorView,在 Activity被建立后会经过 WindowManager将 DecorView添加到 PhoneWindow中而且建立 ViewRootImpl实例,随后将 DecorView与 ViewRootImpl进行关联,最终经过执行 ViewRootImpl的 performTraversals()开启整个View树的绘制。

6.5 Draw 绘制流程

Android的Draw过程能够分为六个步骤:

  1. 首先,绘制View的背景;
  2. 若是须要的话,保持canvas的图层,为fading作准备;
  3. 而后,绘制View的内容;
  4. 接着,绘制View的子View;
  5. 若是须要的话,绘制View的fading边缘并恢复图层;
  6. 最后,绘制View的装饰(例如滚动条等等)。

涉及到的代码以下:

public void draw(Canvas canvas) {
    ...
    // 步骤一:绘制View的背景
    drawBackground(canvas);
    ...
    // 步骤二:若是须要的话,保持canvas的图层,为fading作准备
    saveCount = canvas.getSaveCount();
    ...
    canvas.saveLayer(left, top, right, top + length, null, flags);
    ...
    // 步骤三:绘制View的内容
    onDraw(canvas);
    ...
    // 步骤四:绘制View的子View
    dispatchDraw(canvas);
    ...
    // 步骤五:若是须要的话,绘制View的fading边缘并恢复图层
    canvas.drawRect(left, top, right, top + length, p);
    ...
    canvas.restoreToCount(saveCount);
    ...
    // 步骤六:绘制View的装饰(例如滚动条等等)
    onDrawForeground(canvas)
}

6.6 Requestlayout,onlayout,onDraw,DrawChild区别与联系

  • requestLayout():会致使调用 measure()过程 和 layout()过程,将会根据标志位判断是否须要ondraw。
  • onLayout():若是该View是ViewGroup对象,须要实现该方法,对每一个子视图进行布局。
  • onDraw():绘制视图自己 (每一个View都须要重载该方法,ViewGroup不须要实现该方法)。
  • drawChild():去从新回调每一个子视图的draw()方法。

6.7 invalidate() 和 postInvalidate()的区别

invalidate()与postInvalidate()都用于刷新View,主要区别是invalidate()在主线程中调用,若在子线程中使用须要配合handler;而postInvalidate()可在子线程中直接调用。

7,Android进程

7.1 概念

进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操做系统结构的基础。

当一个程序第一次启动的时候,Android会启动一个LINUX进程和一个主线程。默认的状况下,全部该程序的组件都将在该进程和线程中运行。 同时,Android会为每一个应用程序分配一个单独的LINUX用户。Android会尽可能保留一个正在运行进程,只在内存资源出现不足时,Android会尝试中止一些进程从而释放足够的资源给其余新的进程使用, 也能保证用户正在访问的当前进程有足够的资源去及时地响应用户的事件。

咱们能够将一些组件运行在其余进程中,而且能够为任意的进程添加线程。组件运行在哪一个进程中是在manifest文件里设置的,其中<Activity>,<Service>,<receiver>和<provider>都有一个process属性来指定该组件运行在哪一个进程之中。咱们能够设置这个属性,使得每一个组件运行在它们本身的进程中,或是几个组件共同享用一个进程,或是不共同享用。<application>元素也有一个process属性,用来指定全部的组件的默认属性。

Android中的全部组件都在指定的进程中的主线程中实例化的,对组件的系统调用也是由主线程发出的。每一个实例不会创建新的线程。对系统调用进行响应的方法——例如负责执行用户动做的View.onKeyDown()和组件的生命周期函数——都是运行在这个主线程中的。这意味着当系统调用这个组件时,这个组件不能长时间的阻塞主线程。例如进行网络操做时或是更新UI时,若是运行时间较长,就不能直接在主线程中运行,由于这样会阻塞这个进程中其余的组件,咱们能够将这样的组件分配到新建的线程中或是其余的线程中运行。

7.2 进程生命周期

按照生命周期的不一样,Android的进程能够分为前台进程、后台进程、可见进程、服务进程和空进程等。

前台进程

前台进程是用户当前正在使用的进程,一些前台进程能够在任什么时候候都存在,当内存低的时候前台进程也可能被销毁。对于这种状况下,设备会进行内存调度,停止一些前台进程来保持对用户交互的响应。

若是有如下的情形,那么它就是前台进程:

  1. 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
  2. 托管某个 Service,后者绑定到用户正在交互的 Activity
  3. 托管正在“前台”运行的 Service(服务已调用 startForeground())
  4. 托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
  5. 托管正执行其 onReceive() 方法的 BroadcastReceiver
可见进程

可见进程指的是不包含前台组件,可是会在屏幕上显示一个可见的进程。

若是有以下的一种情形,那就是可见进程:

  1. 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,若是 re前台 Activity启动了一个对话框,容许在其后显示上一 Activity,则有可能会发生这种状况。
  2. 托管绑定到可见(或前台)Activity 的 Service。
服务进程

经过startService() 方法启动的Service,这个Service没有上面的两种进程重要,通常会随着应用的生命周期。

通常来讲,使用 startService() 方法启动的服务且不属于上述两个更高类别进程的就是服务进程。

后台进程

包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。一般会有不少后台进程在运行,所以它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。

空进程

不含任何活动应用组件的进程。保留这种进程的的惟一目的是用做缓存,以缩短下次在其中运行组件所需的启动时间。 为使整体系统资源在进程缓存和底层内核缓存之间保持平衡,系统每每会终止这些进程。

7.3 多进程

首先,进程通常指一个执行单元,在移动设备上就是一个程序或应用,咱们在Android中所说的多进程(IPC)通常指一个应用包含多个进程。之因此要使用多进程有两方面缘由:某些模块因为特殊的需求要运行在单独的进程;增长应用可用的内存空间。

在Android中开启多进程只有一种方法,就是在AndroidManifest.xml中注册Service、Activity、Receiver、ContentProvider时指定android:process属性,以下所示。

<service
    android:name=".MyService"
    android:process=":remote">
</service>

<activity
    android:name=".MyActivity"
    android:process="com.shh.ipctest.remote2">
</activity>

能够看到,MyService和MyActivity指定的android:process属性值有所不一样,它们的区别以下:

  • :remote:以:开头是一种简写,系统会在当前进程名前附件当前包名,完整的进程名为:com.shh.ipctest:remote,同时以:开头的进程属于当前应用的私有进程,其它应用的组件不能和它跑在同一进程。
  • com.shh.ipctest.remote2:这是完整的命名方式,不会附加包名,其它应用若是和该进程的ShareUID、签名相同,则能够和它跑在同一个进程,实现数据共享。

不过,开启多进程会引起以下问题,必须引发注意:

  • 静态成员和单例模式失效
  • 线程同步机制失效
  • SharedPreferences可靠性下降
  • Application被屡次建立

对于前两个问题,能够这么理解,在Android中,系统会为每一个应用或进程分配独立的虚拟机,不一样的虚拟机天然占有不一样的内存地址空间,因此同一个类的对象会产生不一样的副本,致使共享数据失败,必然也不能实现线程的同步。

因为SharedPreferences底层采用读写XML的文件的方式实现,多进程并发的的读写极可能致使数据异常。

Application被屡次建立和前两个问题相似,系统在分配多个虚拟机时至关于把同一个应用从新启动屡次,必然会致使 Application 屡次被建立,为了防止在 Application
中出现无用的重复初始化,可以使用进程名来作过滤,只让指定进程的才进行全局初,以下所示。

public class MyApplication extends Application{
    @Override
    public void onCreate() {
        super.onCreate();
        String processName = "com.xzh.ipctest";
        if (getPackageName().equals(processName)){
            // do some init
        }
    }
}

7.4 多进程通讯方式

目前,Android中支持的多进程通讯方式主要有如下几种:

  • AIDL:功能强大,支持进程间一对多的实时并发通讯,并可实现 RPC (远程过程调用)。
  • Messenger:支持一对多的串行实时通讯, AIDL 的简化版本。
  • Bundle:四大组件的进程通讯方式,只能传输 Bundle 支持的数据类型。
  • ContentProvider:强大的数据源访问支持,主要支持 CRUD 操做,一对多的进程间数据共享,例如咱们的应用访问系统的通信录数据。
  • BroadcastReceiver:即广播,但只能单向通讯,接收者只能被动的接收消息。
    文件共享:在非高并发状况下共享简单的数据。
  • Socket:经过网络传输数据。

8,序列化

8.1 Parcelable 与 Serializable

  • Serializable 使用 I/O 读写存储在硬盘上,而 Parcelable 是直接在内存中读写。
  • Serializable 会使用反射,序列化和反序列化过程须要大量 I/O 操做, Parcelable 自已实现封送和解封(marshalled &unmarshalled)操做不须要用反射,数据也存放在 Native 内存中,效率要快不少。

8.2 示例

Serializable实例:

import java.io.Serializable;
class serializableObject implements Serializable {
   String name;
   public serializableObject(String name) {
      this.name = name;
   }
   public String getName() {
      return name;
   }
}

Parcelable实例:

import android.os.Parcel;
import android.os.Parcelable;
class parcleObject implements Parcelable {
   private String name;
   protected parcleObject(Parcel in) {
      this.name = in.readString();
   }
   public parcleObject(String name) {
      this.name = name;
   }
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public static final Creator<parcleObject> CREATOR = new Creator<parcleObject>() {
      @Override
      public parcleObject createFromParcel(Parcel in) {
         return new parcleObject(in);
      }
      @Override
      public parcleObject[] newArray(int size) {
         return new parcleObject[size];
      }
   };
   @Override
   public int describeContents() {
      return 0;
   }
   @Override
   public void writeToParcel(Parcel dest, int flags) {
      dest.writeString(this.name);
   }
}

使用Parcelable时,通常须要用到如下几个方法:

  • createFromParcel(Parcel in):从序列化后的对象中建立原始对象。
  • newArray(int size):建立指定长度的原始对象数组。
  • User(Parcel in) 从序列化后的对象中建立原始对象。
  • writeToParcel(Parcel dest, int flags):将当前对象写入序列化结构中,其中 flags 标识有两种值:0 或者 1。为 1 时标识当前对象须要做为返回值返回,不能当即释放资源,几乎全部状况都为 0。
  • describeContents:返回当前对象的内容描述。若是含有文件描述符,返回 1,不然返回 0,几乎全部状况都返回 0。

9,Window

9.1 基本概念

Window 是一个抽象类,它的具体实现是 PhoneWindow。WindowManager 是外界访问 Window 的入口,Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 过程。Android 中全部的视图都是经过 Window 来呈现,所以 Window 实际是 View 的直接管理者。

依据做用的不一样,Window能够分为以下几种:

  • Application Window:对应着一个 Activity;
  • Sub Window: 不能单独存在,只能附属在父 Window 中,如 Dialog 等;
  • System Window:须要权限声明,如 Toast 和 系统状态栏等;

9.2 内部机制

Window 是一个抽象的概念,每个 Window 对应着一个 View 和一个 ViewRootImpl。Window 实际是不存在的,它是以 View 的形式存在。对 Window 的访问必须经过 WindowManager,WindowManager 的实现类是 WindowManagerImpl,源码以下:

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.updateViewLayout(view, params);
}

@Override
public void removeView(View view) {
    mGlobal.removeView(view, false);
}

WindowManagerImpl 没有直接实现 Window 的三大操做,而是所有交给 WindowManagerGlobal 处理,WindowManagerGlobal 以工厂的形式向外提供本身的实例,涉及的代码以下:

// 添加
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ···
    // 子 Window 的话须要调整一些布局参数
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        ···
    }
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        // 新建一个 ViewRootImpl,并经过其 setView 来更新界面完成 Window 的添加过程
        ···
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

// 删除
@UnsupportedAppUsage
public void removeView(View view, boolean immediate) {
    ···
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        View curView = mRoots.get(index).getView();
        removeViewLocked(index, immediate);
        ···
    }
}

private void removeViewLocked(int index, boolean immediate) {
    ViewRootImpl root = mRoots.get(index);
    View view = root.getView();
    if (view != null) {
        InputMethodManager imm = InputMethodManager.getInstance();
        if (imm != null) {
            imm.windowDismissed(mViews.get(index).getWindowToken());
        }
    }
    boolean deferred = root.die(immediate);
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
            mDyingViews.add(view);
        }
    }
}

// 更新
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    ···
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    view.setLayoutParams(wparams);
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false);
    }
}

10,消息机制

谈Android的消息机制主要就是Handler机制。

10.1 Handler 机制

Handler 有两个主要用途:

  • 安排 Message 和 runnables 在未来的某个时刻执行;
  • 将要在不一样于本身的线程上执行的操做排入队列。(在多个线程并发更新UI的同时保证线程安全。)

Android 规定访问 UI 只能在主线程中进行,由于 Android 的 UI 控件不是线程安全的,多线程并发访问会致使 UI 控件处于不可预期的状态。为何系统不对 UI 控件的访问加上锁机制?缺点有两个:加锁会让 UI 访问的逻辑变得复杂;其次锁机制会下降 UI 访问的效率。若是子线程访问 UI,那么程序就会抛出异常。为了保证线程安全,ViewRootImpl 对UI操做作了验证,这个验证工做是由 ViewRootImpl的 checkThread 方法完成。

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

谈Handler机制时,一般会包含如下三个对象:

  • Message:Handler 接收和处理的消息对象。
  • MessageQueue:Message 的队列,先进先出,每个线程最多能够拥有一个。
  • Looper:消息泵,是 MessageQueue 的管理者,会不断从 MessageQueue 中取出消息,并将消息分给对应的 Handler 处理,每一个线程只有一个 Looper。

Handler 建立的时候会采用当前线程的 Looper 来构造消息循环系统,须要注意的是,线程默认是没有 Looper 的,直接使用 Handler 会报错,若是须要使用 Handler 就必须为线程建立 Looper,由于默认的 UI 主线程,也就是 ActivityThread,ActivityThread 被建立的时候就会初始化 Looper,这也是在主线程中默承认以使用 Handler 的缘由。

10.2 工做原理

ThreadLocal
ThreadLocal 是一个线程内部的数据存储类,经过它能够在指定的线程中存储数据,其余线程则没法获取。Looper、ActivityThread 以及 AMS 中都用到了 ThreadLocal。当不一样线程访问同一个ThreadLocal 的 get方法,ThreadLocal 内部会从各自的线程中取出一个数组,而后再从数组中根据当前 ThreadLcoal 的索引去查找对应的value值,源码以下:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

···
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

MessageQueue
MessageQueue主要包含两个操做:插入和读取。读取操做自己会伴随着删除操做,插入和读取对应的方法分别是 enqueueMessage 和 next。MessageQueue 内部实现并非用的队列,实际上经过一个单链表的数据结构来维护消息列表。next 方法是一个无限循环的方法,若是消息队列中没有消息,那么 next 方法会一直阻塞。当有新消息到来时,next 方法会放回这条消息并将其从单链表中移除,源码以下。

boolean enqueueMessage(Message msg, long when) {
    ···
    synchronized (this) {
        ···
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
···
Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    ···
    for (;;) {
        ···
        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
            ···
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

Looper
Looper 会不停地从 MessageQueue 中 查看是否有新消息,若是有新消息就会马上处理,不然会一直阻塞,Looper源码。

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

可经过 Looper.prepare() 为当前线程建立一个 Looper,默认状况下,Activity会建立一个Looper对象。

除了 prepare 方法外,Looper 还提供了 prepareMainLooper 方法,主要是给 ActivityThread 建立 Looper 使用,本质也是经过 prepare 方法实现的。因为主线程的 Looper 比较特殊,因此 Looper 提供了一个 getMainLooper 方法来获取主线程的 Looper。

同时,Looper 提供了 quit 和 quitSafely 来退出一个 Looper,两者的区别是:quit 会直接退出 Looper,而 quitSafly 只是设定一个退出标记,而后把消息队列中的已有消息处理完毕后才安全地退出。Looper 退出后,经过 Handler 发送的消息会失败,这个时候 Handler 的 send 方法会返回 false,所以在不须要的时候须要及时终止 Looper。

Handler
Handler 的工做主要包含消息的发送和接收的过程。消息的发送能够经过 post/send 的一系列方法实现,post 最终也是经过send来实现的。

11, RecyclerView优化

在Android开发中,常常会遇到长列表的问题,所以不少时候,就会涉及到RecyclerView的优化问题。对于列表卡顿的缘由,一般有以下一些:

11.1 卡顿场景

notifyDataSetChanged
若是数据须要全局刷新时,可使用notifyDataSetChanged;对于增长或减小数据,可使用局部刷新,以下所示。

void onNewDataArrived(List<News> news) {
    List<News> oldNews = myAdapter.getItems();
    DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news));
    myAdapter.setNews(news);
    result.dispatchUpdatesTo(myAdapter);
}

RecycleView嵌套
在实际开发中,常常会看到竖直滚动的RecycleView嵌套一个横向滚动的RecycleView的场景。因为单个RecycleView都拥有独立的itemView对象池,对于嵌套的状况,能够设置共享对象池,以下。

class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
    RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();

    ...

    @Override
    public void onCreateViewHolder(ViewGroup parent, int viewType) {
        // inflate inner item, find innerRecyclerView by ID…
        LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(),
                LinearLayoutManager.HORIZONTAL);
        innerRv.setLayoutManager(innerLLM);
        innerRv.setRecycledViewPool(mSharedPool);
        return new OuterAdapter.ViewHolder(innerRv);

    }

嵌套层级过深
经过Systemtrace工具能够检测Layout的性能,若是耗时太长或者调用次数过多,须要考察一下是否过分使用RelativeLayout或者嵌套多层LinearLayout,每层Layout都会致使其child屡次的measure/layout。

对象分配和垃圾回收
虽然Android 5.0上使用ART来减小GC停顿时间,但仍然会形成卡顿。尽可能避免在循环内建立对象致使GC。要知道,建立对象须要分配内存,而这个时机会检查内存是否足够来决定需不须要进行GC。

11.2 其余优化策略

除了上面的典型场景外,RecyclerView的优化还须要注意如下几点:

  • 升级 RecycleView 版本到 25.1.0 及以上使用 Prefetch 功能;
  • 经过重写 RecyclerView.onViewRecycled(holder) 来回收资源;
  • 若是 Item 高度是固定的话,可使用 RecyclerView.setHasFixedSize(true); 来避免
    requestLayout 浪费资源;
  • 对 ItemView 设置监听器,不要对每一个 Item 都调用 addXxListener,应该你们公用一个 XxListener,根据 ID 来进行不一样的操做,优化了对象的频繁建立带来的资源消耗;
  • 尽可能不要对ViewHolder使用有透明度改变的绘制;
  • 增长预加载策略。
相关文章
相关标签/搜索