正常情况系,Activity会经历如下几个阶段:
如果需要相关的Android基础和进阶资料以及体系大纲图可以点击 GitHub自行获取
同时我也推荐一下我的Android交流裙Android技术交流,不仅可以免费获取资料,而且还能有一帮大佬为你摆平关于Android的技术问题~
对于生命周期,通常还会问如下的一些问题:
Activity的启动模式有四种:Standard、SingleTop、SingleTask和SingleInstance。
在理解Activity的启动流程之前,先让我们来看一下Android系统启动流程。总的来说,Android系统启动流程的主要经历init进程 -> Zygote进程 –> SystemServer进程 –> 各种系统服务 –> 应用进程等阶段。
Launcher进程启动后,就会调用Activity的启动了。首先,Launcher会调用ActivityTaskManagerService,然后ActivityTaskManagerService会调用ApplicationThread,然后ApplicationThread再通过ActivityThread启动Activity
项目地址:GitHub
Fragment,是Android 3.0(API 11)提出的,为了兼容低版本,support-v4库中也开发了一套Fragment API,最低兼容Android 1.6,如果要在最新的版本中使用Fragment,需要引入AndroidX的包。
相比Activity,Fragment具有如下一些特点:
Fragment有如下几个核心的类:
Fragment必须是依存于Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。相比Activity的生命周期,Fragment的生命周期如下所示。
如下图所示。
下面是Activity的生命周期和Fragment的各个生命周期方法的对应关系。
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;
}
Service的启动方式主要有两种,分别是startService和bindService。
其中,StartService使用的是同一个Service,因此onStart()会执行多次,onCreate()只执行一次,onStartCommand()也会执行多次。使用bindService启动时,onCreate()与onBind()都只会调用一次。
使用startService启动时是单独开一个服务,与Activity没有任何关系,而bindService方式启动时,Service会和Activity进行绑定,当对应的activity销毁时,对应的Service也会销毁。
下图是startService和bindService两种方式启动Service的示意图。
3.2.1 startService
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实例本身。
现在,由于系统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参数。
BroadcastReceiver,广播接收者,它是一个系统全局的监听器,用于监听系统全局的Broadcast消息,所以它可以很方便的进行系统组件之间的通信。BroadcastReceiver属于系统级的监听器,它拥有自己的进程,只要存在与之匹配的Broadcast被以Intent的形式发送出来,BroadcastReceiver就会被激活。
和其他的四大组件一样,BroadcastReceiver也有自己独立的声明周期,但是它又和Activity、Service不同。当在系统注册一个BroadcastReceiver之后,每次系统以一个Intent的形式发布Broadcast的时候,系统都会创建与之对应的BroadcastReceiver广播接收者实例,并自动触发它的onReceive()方法,当onReceive()方法被执行完成之后,BroadcastReceiver的实例就会被销毁。
从不同的纬度区分,BroadcastReceiver可以分为不同的类别。
4.2.1 注册广播
广播的注册分为静态注册和动态注册。静态注册是在Mainfest清单文件中进行注册,比如。
动态注册是在代码中,使用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()
}
}
}
}
ContentProvider是Android四大组件之一,不过平时使用的机会比较少。如果你看过它的底层源码,那么就应该知道ContentProvider是通过Binder进行数据共享。因此,如果我们需要对第三方应用提供数据,可以考虑使用ContentProvider实现。
Android本身的View体系非常庞大的,如果要完全弄懂View的原理是很困难的,我们这里捡一些比较重要的概念来给大家讲解。
Android View本身的绘制流程需要经过measure测量、layout布局、draw绘制三个过程,最终才能够将其绘制出来并展示在用户面前。
首先,我们看一下Android的MeasureSpec,Android的MeasureSpec分为3中模式,分别是EXACTLY、AT_MOST 和 UNSPECIFIED,含义如下。
Android的事件分发由dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三个方法构成。
如果该View存在子View且点击到了该子View,则不拦截,继续分发 给子View 处理,此时相当于return false。如果该View没有子View或者有子View但是没有点击中子View,则交由该View的onTouchEvent响应,此时相当于return true。
在Android系统中,拥有事件传递处理能力的类有以下三种:
在事件分发中,有时候会问:ACTION_CANCEL什么时候触发,触摸button然后滑动到外部抬起会触发点击事件吗,再滑动回去抬起会么?
对于这个问题,我们需要明白以下内容:
6.3 MotionEvent
Android的MotionEvent事件主要有以下几个:
下面是事件的举例:点击屏幕后松开,事件序列为 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过程可以分为六个步骤:
涉及到的代码如下:
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区别与联系
6.7 invalidate() 和 postInvalidate()的区别
invalidate()与postInvalidate()都用于刷新View,主要区别是invalidate()在主线程中调用,若在子线程中使用需要配合handler;而postInvalidate()可在子线程中直接调用。
7.1 概念
进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
当一个程序第一次启动的时候,Android会启动一个LINUX进程和一个主线程。默认的情况下,所有该程序的组件都将在该进程和线程中运行。 同时,Android会为每个应用程序分配一个单独的LINUX用户。Android会尽量保留一个正在运行进程,只在内存资源出现不足时,Android会尝试停止一些进程从而释放足够的资源给其他新的进程使用, 也能保证用户正在访问的当前进程有足够的资源去及时地响应用户的事件。
我们可以将一些组件运行在其他进程中,并且可以为任意的进程添加线程。组件运行在哪个进程中是在manifest文件里设置的,其中,,和都有一个process属性来指定该组件运行在哪个进程之中。我们可以设置这个属性,使得每个组件运行在它们自己的进程中,或是几个组件共同享用一个进程,或是不共同享用。元素也有一个process属性,用来指定所有的组件的默认属性。
Android中的所有组件都在指定的进程中的主线程中实例化的,对组件的系统调用也是由主线程发出的。每个实例不会建立新的线程。对系统调用进行响应的方法——例如负责执行用户动作的View.onKeyDown()和组件的生命周期函数——都是运行在这个主线程中的。这意味着当系统调用这个组件时,这个组件不能长时间的阻塞主线程。例如进行网络操作时或是更新UI时,如果运行时间较长,就不能直接在主线程中运行,因为这样会阻塞这个进程中其他的组件,我们可以将这样的组件分配到新建的线程中或是其他的线程中运行。
7.2 进程生命周期
按照生命周期的不同,Android的进程可以分为前台进程、后台进程、可见进程、服务进程和空进程等。
前台进程
前台进程是用户当前正在使用的进程,一些前台进程可以在任何时候都存在,当内存低的时候前台进程也可能被销毁。对于这种情况下,设备会进行内存调度,中止一些前台进程来保持对用户交互的响应。
如果有以下的情形,那么它就是前台进程:
可见进程
可见进程指的是不包含前台组件,但是会在屏幕上显示一个可见的进程。
如果有如下的一种情形,那就是可见进程:
服务进程
通过startService() 方法启动的Service,这个Service没有上面的两种进程重要,一般会随着应用的生命周期。
一般来说,使用 startService() 方法启动的服务且不属于上述两个更高类别进程的就是服务进程。
后台进程
包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。
空进程
不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
7.3 多进程
首先,进程一般指一个执行单元,在移动设备上就是一个程序或应用,我们在Android中所说的多进程(IPC)一般指一个应用包含多个进程。之所以要使用多进程有两方面原因:某些模块由于特殊的需求要运行在单独的进程;增加应用可用的内存空间。
在Android中开启多进程只有一种方法,就是在AndroidManifest.xml中注册Service、Activity、Receiver、ContentProvider时指定android:process属性,如下所示。
可以看到,MyService和MyActivity指定的android:process属性值有所不同,它们的区别如下:
不过,开启多进程会引发如下问题,必须引起注意:
对于前两个问题,可以这么理解,在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中支持的多进程通信方式主要有以下几种:
文件共享:在非高并发情况下共享简单的数据。
8.1 Parcelable 与 Serializable
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 CREATOR = new Creator() {
@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时,一般需要用到以下几个方法:
项目地址:GitHub
9.1 基本概念
Window 是一个抽象类,它的具体实现是 PhoneWindow。WindowManager 是外界访问 Window 的入口,Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 过程。Android 中所有的视图都是通过 Window 来呈现,因此 Window 实际是 View 的直接管理者。
依据作用的不同,Window可以分为如下几种:
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);
}
}
谈Android的消息机制主要就是Handler机制。
10.1 Handler 机制
Handler 有两个主要用途:
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机制时,通常会包含以下三个对象:
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来实现的。
在Android开发中,经常会遇到长列表的问题,因此很多时候,就会涉及到RecyclerView的优化问题。对于列表卡顿的原因,通常有如下一些:
11.1 卡顿场景
notifyDataSetChanged 如果数据需要全局刷新时,可以使用notifyDataSetChanged;对于增加或减少数据,可以使用局部刷新,如下所示。
void onNewDataArrived(List news) {
List 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 {
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的优化还需要注意以下几点:
如果需要相关的资料以及体系大纲图可以点击 GitHub自行获取