手把手带你解读源码,让你知道androidx库究竟是如何做到兼容低版本的?

androidx库是谷歌在两年前推出的用于取代support库的一个库,最低兼容API 28,也就是说28以上的默认使用androidx库,之前的support库都不再适用了。如果你不懂support库有哪些内容,以及如何迁移到androidx?可以看看我之前写的文章 《AndroidX库和一般库的详细对比以及迁移中遇到的坑总结》

言归正传,下面我带领大家来看看androidx库是如何做到兼容低版本的。

首先我们这里有一个MainActivity,它是继承自androidx.appcompat.app.AppCompatActivity,但是AppCompatActivity源码太多,从头到尾去看会一脸懵逼,所以我们可以换一种最简易的思路去看源码。我们先看看我们创建项目之后默认的MainActivity里面的代码是什么样的:

package com.lzw.androidx_demo;

import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}
}

我们看到默认的代码重写了onCreate函数,调用了父类(AppCompatActivity)的onCreate函数,我们点击去看看:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    super.onCreate(savedInstanceState);
}

首先它调用了getDelegate()方法,返回一个AppCompatDelegate对象,调用了delegateonCreate方法,最后再调用父类的onCreate方法。

我们就从生命周期方法开始看,再看看其它的相关方法,我们会发现都首先调用了getDelegate(),返回一个AppCompatDelegate对象。源码摘抄如下:

 @Override
protected void onStart() {
    super.onStart();
    getDelegate().onStart();
}

@Override
protected void onStop() {
    super.onStop();
    getDelegate().onStop();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    getDelegate().onDestroy();
}

当然如果你去看其他的方法,内部第一行就是调用了``getDelegate方法,返回一个AppCompatDelegate对象。下面重点看一下这个getDelegate`方法。

1.AppCompatActivity的getDelegate()方法

1.getDelegate()方法

它的返回值是AppCompatDelegate类,它有一个成员变量mDelegate,它持有的AppCompatDelegate类的引用,如果为空,就调用AppCompatDelegate.create(this, this)创建一个AppCompatDelegate类的对象,否则就用mDelegate。源码如下:

/**
 * @return The {@link AppCompatDelegate} being used by this Activity.
 */
@NonNull
public AppCompatDelegate getDelegate() {
	if (mDelegate == null) {
		mDelegate = AppCompatDelegate.create(this, this);
	}
	return mDelegate;
}

2.接下来我们看看create(xxx)方法内部的流程,先附上源码:

@NonNull
public static AppCompatDelegate create(@NonNull Activity activity,
		@Nullable AppCompatCallback callback) {
	return new AppCompatDelegateImpl(activity, callback);
}

我们可以看到create方法内部直接创建了AppCompatDelegateImpl类的对象。我们看源码得知AppCompatDelegate是一个抽象类。这里面的创建对象的过程实际上是使用的AppCompatDelegate的子类AppCompatDelegateImpl来实现的。

3.接下来我们看看AppCompatDelegateImpl,看它做了什么事情:

它有几个构造函数,这里选取重要的两个:

AppCompatDelegateImpl(Activity activity, AppCompatCallback callback) {
    this(activity, null, callback, activity);
}

private AppCompatDelegateImpl(Context context, Window window, AppCompatCallback callback,
        Object host) {
    mContext = context;
    mAppCompatCallback = callback;
    mHost = host;

    if (mLocalNightMode == MODE_NIGHT_UNSPECIFIED && mHost instanceof Dialog) {
        final AppCompatActivity activity = tryUnwrapContext();
        if (activity != null) {
            // This code path is used to detect when this Delegate is a child Delegate from
            // an Activity, primarily for Dialogs. Dialogs use the Activity as it's Context,
            // so we want to make sure that the this 'child' delegate does not interfere
            // with the Activity config. The simplest way to do that is to match the
            // outer Activity's local night mode
            mLocalNightMode = activity.getDelegate().getLocalNightMode();
        }
    }
    if (mLocalNightMode == MODE_NIGHT_UNSPECIFIED) {
        // Try and read the current night mode from our static store
        final Integer value = sLocalNightModes.get(mHost.getClass().getName());
        if (value != null) {
            mLocalNightMode = value;
            // Finally remove the value
            sLocalNightModes.remove(mHost.getClass().getName());
        }
    }

    if (window != null) {
        attachToWindow(window);
    }

    // Preload appcompat-specific handling of drawables that should be handled in a special
    // way (for tinting etc). After the following line completes, calls from AppCompatResources
    // to ResourceManagerInternal (in appcompat-resources) will handle those internal drawable
    // paths correctly without having to go through AppCompatDrawableManager APIs.
    AppCompatDrawableManager.preload();
}

两个参数的构造函数调用了4个参数的构造函数。

构造函数内部第一行代码是将传进来的context赋值给本类的成员变量mContext,我们都只到context是核心,它可以操作很多内容,比如四大组件,资源文件等都需要用到context,所以这个mContext我们重点关注一下。

看来这个AppCompatDelegate类非常关键。

2.AppCompatDelegate类的findViewById方法

看看我们这
里面有一个TextView控件,一般情况下我们首先要findViewById找到它的id。代码如下:

package com.lzw.androidx_demo;

import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		TextView textView = findViewById(R.id.tv_title);
	}
}

说明一下:我这里使用的编译版本是29,所以后文看到的源码API版本都是基于29的,但是大部分源码都是差不多的,只有少量的是差异化处理(如果你使用了其它的版本,可能源码有些许不同):

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"
    defaultConfig {
        applicationId "com.lzw.androidx_demo"
        minSdkVersion 28
        targetSdkVersion 29
    }
}

那么我们废话少说,就直接从findViewById开始看源码,我们点击去看看源码(Ctrl+鼠标左键),这时候我们打开了AppconpatActivityfindViewById方法:

@SuppressWarnings("TypeParameterUnusedInFormals")
@Override
public <T extends View> T findViewById(@IdRes int id) {
	return getDelegate().findViewById(id);
}

我们把可以看到这里面调用了getDelegate()这个方法,它的返回值是AppCompatDelegate,然后再通过AppCompatDelegate这个类调用其静态方法findViewById。我们这里面就开始分两步来讲解: