关于android:Android-面试之必问Android基础

47次阅读

共计 25506 个字符,预计需要花费 64 分钟才能阅读完成。

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

1,Activity

1.1 生命周期

失常状况系,Activity 会经验如下几个阶段:

  • onCreate:示意 Activity 正在被创立。
  • onRestart:示意 Activity 正在被重新启动。
  • onStart:示意 Activity 正在被启动,这时曾经可见,但没有呈现在前台无奈进行交互。
  • onResume:示意 Activity 曾经可见,并且处于前台。
  • onPause:示意 Activity 正在进行(可做一次保留状态进行动画等非耗时操作)。
  • onStop:示意 Activity 行将进行(可进行重量级回收工作)。
  • onDestroy:示意 Activity 行将被销毁。


对于生命周期,通常还会问如下的一些问题:

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

1.2 启动模式

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

  • 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 过程启动

Launcher 过程启动后,就会调用 Activity 的启动了。首先,Launcher 会调用 ActivityTaskManagerService,而后 ActivityTaskManagerService 会调用 ApplicationThread,而后 ApplicationThread 再通过 ActivityThread 启动 Activity,残缺的剖析能够参考 Android 之 Activity 启动流程

2,Fragment

2.1 简介

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

相比 Activity,Fragment 具备如下一些特点:

  • 模块化(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 应用有透明度扭转的绘制;
  • 减少预加载策略。

正文完
 0