在上一篇文章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过程 –> 各种零碎服务 –> 利用过程等阶段。
- 启动电源以及系统启动:当电源按下时疏导芯片从预约义的中央(固化在ROM)开始执行,加载疏导程序BootLoader到RAM,而后执行。
- 疏导程序BootLoader:BootLoader是在Android零碎开始运行前的一个小程序,次要用于把零碎OS拉起来并运行。
- Linux内核启动:当内核启动时,设置缓存、被爱护存储器、打算列表、加载驱动。当其实现零碎设置时,会先在系统文件中寻找init.rc文件,并启动init过程。
- init过程启动:初始化和启动属性服务,并且启动Zygote过程。
- Zygote过程启动:创立JVM并为其注册JNI办法,创立服务器端Socket,启动SystemServer过程。
- SystemServer过程启动:启动Binder线程池和SystemServiceManager,并且启动各种零碎服务。
- 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过程能够分为六个步骤:
- 首先,绘制View的背景;
- 如果需要的话,放弃canvas的图层,为fading做筹备;
- 而后,绘制View的内容;
- 接着,绘制View的子View;
- 如果需要的话,绘制View的fading边缘并复原图层;
- 最初,绘制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的过程能够分为前台过程、后盾过程、可见过程、服务过程和空过程等。
前台过程
前台过程是用户以后正在应用的过程,一些前台过程能够在任何时候都存在,当内存低的时候前台过程也可能被销毁。对于这种状况下,设施会进行内存调度,停止一些前台过程来放弃对用户交互的响应。
如果有以下的情景,那么它就是前台过程:
- 托管用户正在交互的 Activity(已调用 Activity 的 onResume() 办法)
- 托管某个 Service,后者绑定到用户正在交互的 Activity
- 托管正在“前台”运行的 Service(服务已调用 startForeground())
- 托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
- 托管正执行其 onReceive() 办法的 BroadcastReceiver
可见过程
可见过程指的是不蕴含前台组件,然而会在屏幕上显示一个可见的过程。
如果有如下的一种情景,那就是可见过程:
- 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 办法)。例如,如果 re前台 Activity启动了一个对话框,容许在其后显示上一 Activity,则有可能会产生这种状况。
- 托管绑定到可见(或前台)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应用有透明度扭转的绘制;
- 减少预加载策略。
发表回复