Jetpact-activity元件完全解析

2020-11-13 11:02:31

Jetpact-activity元件完全解析

前言

Jetpack-activity/fragment 是jetpack架構元件中最基礎的部分。

底層對jetpack-lifecycle元件做了支援、為開發者能夠直接使用jetpack架構元件提供了支援,因此要想徹底瞭解jetpack系列,先學習activity/fragment元件很有必要。

注意:
本文中原始碼均使用 1.1.0穩定版本

參照

def activity_version = "1.1.0"
// Java language implementation
implementation "androidx.activity:activity:$activity_version"
// Kotlin
implementation "androidx.activity:activity-ktx:$activity_version"

繼承結構:

在這裡插入圖片描述

原始碼解析

/**
 * Base class for activities that enables composition of higher level components.
 * <p>
 * Rather than all functionality being built directly into this class, only the minimal set of
 * lower level building blocks are included. Higher level components can then be used as needed
 * without enforcing a deep Activity class hierarchy or strong coupling between components.
 */
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner {
        //....
}        

ComponentActivity 類的註釋上可以得出兩條資訊:

  1. ComponentActivity 是一個支援組合高階元件的基礎類別Activity
  2. 並沒有將所有的元件都構建到這個類中,只是包含最基礎的底層元件。開發者可以根據需要使用更高階別的元件,無需在元件之間強耦合。

構造器

ComponentActivity在構造器中針對Android不同版本進行了簡單相容處理


public ComponentActivity() {
    Lifecycle lifecycle = getLifecycle();
    //noinspection ConstantConditions
    //如果在使用 Lifecycle 物件的時候還沒有初始化則直接拋錯,對於重寫 getLifecycle() 方法需要注意
    if (lifecycle == null) {
        throw new IllegalStateException("getLifecycle() returned null in ComponentActivity's "
                + "constructor. Please make sure you are lazily constructing your Lifecycle "
                + "in the first call to getLifecycle() rather than relying on field "
                + "initialization.");
    }
    //針對API 19以上相容
    if (Build.VERSION.SDK_INT >= 19) {
        getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_STOP) {
                    Window window = getWindow();
                    final View decor = window != null ? window.peekDecorView() : null;
                    if (decor != null) {
                        decor.cancelPendingInputEvents();
                    }
                }
            }
        });
    }
    //Activity銷燬時清除ViewMode中資料
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_DESTROY) {
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
    //針對 19~23 版本 解決 InputMethodManager中 mNextServedView 記憶體漏失問題
    if (19 <= SDK_INT && SDK_INT <= 23) {
        getLifecycle().addObserver(new ImmLeaksCleaner(this));
    }
}

從構造器中程式碼來看做的事情比較多,但都是相容性處理,有三個方面的處理:

  • 針對API19以上相容Activity停止時取消View還未執行的事件
  • 針對API19以上Activity銷燬時清除ViewMode中資料
  • 針對API19~23版本Activity銷燬時解決InputMethodManager中 mNextServedView持有Activity導致記憶體漏失問題

Activity停止時取消View還未執行的事件

Activity 處於stop 時使用者是看不到介面的,也沒有必要再處理 View的點選、長按、動畫等事件。所以有必要將這些事件移除掉。

我們跟隨原始碼看一下具體是怎麼做的:

View層處理

上述程式碼會呼叫ViewcancelPendingInputEvents() 這是個取消事件的總排程方法,它沒有具體做事情,而是呼叫了 dispatchCancelPendingInputEvents() 來完成工作

public final void cancelPendingInputEvents() {
    dispatchCancelPendingInputEvents();
}
void dispatchCancelPendingInputEvents() {
    //位元運算設定標誌位
    mPrivateFlags3 &= ~PFLAG3_CALLED_SUPER;
    //執行清除事件工作
    onCancelPendingInputEvents();
    //檢查標誌位是否正確 以確保完成了清除工作
    if ((mPrivateFlags3 & PFLAG3_CALLED_SUPER) != PFLAG3_CALLED_SUPER) {
        throw new SuperNotCalledException("View " + getClass().getSimpleName() +
                " did not call through to super.onCancelPendingInputEvents()");
    }
}

dispatchCancelPendingInputEvents() 方法中呼叫了 onCancelPendingInputEvents() 來完成具體的清除工作:

onCancelPendingInputEvents() 會清除已傳送到訊息佇列的事件,延遲事件等 如果是自定義View 是可以重寫此方法,來自定義指定那些事件是需要清除或保留的,但是需要注意要 super.onCancelPendingInputEvents() 要呼叫父類別方法 完成 mPrivateFlags3變數的位元運算

public void onCancelPendingInputEvents() {
    //移除點選事件回撥
    removePerformClickCallback();
    //取消等待的長按事件
    cancelLongPress();
    //對標識變數進行操作 在 dispatchCancelPendingInputEvents()對此變數有檢查操作
    mPrivateFlags3 |= PFLAG3_CALLED_SUPER;
}
移除點選事件回撥

removePerformClickCallback() 中直接呼叫 removeCallbacksmPerformClick點選事件傳入

 @UnsupportedAppUsage
 private void removePerformClickCallback() {
     if (mPerformClick != null) {
         removeCallbacks(mPerformClick);
     }
 }

removeCallbacks(Runnable action)才是真正移除事件處理的方法,凡是以下幾種方式新增的事件或延遲事件都會移除

  1. post()
  2. postDelayed()
  3. postOnAnimation()
  4. postOnAnimationDelayed()
public boolean removeCallbacks(Runnable action) {
    if (action != null) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            //移除指定回撥
            attachInfo.mHandler.removeCallbacks(action);
            //移除Choreographer中動畫回撥
            attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
                    Choreographer.CALLBACK_ANIMATION, action, null);
        }
        //移除等待佇列中事件
        getRunQueue().removeCallbacks(action);
    }
    return true;
}
移除長按事件

cancelLongPress()中會分別呼叫 removeLongPressCallback() 清除長按回撥 和 removeTapCallback()移除長按產生的超時事件

removeLongPressCallback()removeTapCallback() 都會呼叫 removeCallbacks(Runnable action) 來移除指定的事件,前面我們已經分析過了,就不再贅述了。

public void cancelLongPress() {
    //移除長按回撥事件
    removeLongPressCallback();
    //移除長按超時回撥事件
    removeTapCallback();
}
//移除長按回撥
private void removeLongPressCallback() {
    if (mPendingCheckForLongPress != null) {
        removeCallbacks(mPendingCheckForLongPress);
    }
}
//移除長按超時事件、修改標誌位
private void removeTapCallback() {
    if (mPendingCheckForTap != null) {
        mPrivateFlags &= ~PFLAG_PREPRESSED;
        removeCallbacks(mPendingCheckForTap);
    }
}

Activity銷燬時清除ViewMode中資料

Activity銷燬時清除ViewMode中資料,需要依賴另一個元件-Lifecycle支援。不僅是 ViewModel 在Jetpack 架構元件中很多元件都需要依賴 Lifecycle元件。

我們重新看一下以下程式碼

//Activity銷燬時清除ViewMode中資料
getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            if (!isChangingConfigurations()) {
                getViewModelStore().clear();
            }
        }
    }
});

我們看到 !isChangingConfigurations() 為 true ,也就是Activity設定沒有修改情況下,在Activity銷燬時會呼叫 getViewModelStore().clear()

這裡我們先將ViewModel放一放,來看一下為什麼在清除 ViewModel中資料還有一個前置條件?這個條件什麼時候滿足條件?

isChangingConfigurations() 相關

isChangingConfigurations()Activity類中方法,用來判斷 Activity 的設定資訊是否更改了,(比如 橫豎屏切換、語言發生變化等)需要重新啟動該Activity的時候 這個方法會返回 true 、沒有更改和預設情況都是 false

/** true if the activity is being destroyed in order to recreate it with a new configuration */
/*package*/ boolean mChangingConfigurations = false;

public boolean isChangingConfigurations() {
    return mChangingConfigurations;
}

我們看到 mChangingConfigurations變數是包級存取許可權,我們知道 Activity資源發生變化時會重新啟動,在 framework層經過一系列呼叫,最終會呼叫 ActivityThreadhandleRelaunchActivity() 將 mChangingConfigurations 設定為 true

@Override
public void handleRelaunchActivity(ActivityClientRecord tmp,
                                   PendingTransactionActions pendingActions) {
    //.....
    int configChanges = 0;
    ActivityClientRecord r = mActivities.get(tmp.token);
    r.activity.mConfigChangeFlags |= configChanges;
    r.mPreserveWindow = tmp.mPreserveWindow;
    //將標識設定為修改
    r.activity.mChangingConfigurations = true;
    //重新啟動Activity
    handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
            pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
    //.....
}                                                                                                  

所以這就是為什麼ViewModel能夠在Activity橫豎屏切換,還能儲存資料不丟失的原因。

getViewModelStore()

再回到 ComponentActivity 中我們看一下 getViewModelStore() 是如何實現的

@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                + "Application instance. You can't request ViewModel before onCreate call.");
    }
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        //從上一次儲存的設定修改中恢復 ViewModelStore 範例       
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        //如果還是null new出一個ViewModelStore範例
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

我們看到 mViewModelStore是個全域性變數,在進行範例化時會先從 上一次儲存的 NonConfigurationInstances 物件中恢復,如果為null 最終會重新 new 出來一個新的 ViewModelStore 範例並賦值給 mViewModelStore

NonConfigurationInstancesComponentActivity 中的靜態內部類 定義如下

static final class NonConfigurationInstances {
    Object custom;
    ViewModelStore viewModelStore;
}

Activity 非正常銷燬時會觸發 onRetainNonConfigurationInstance() 來儲存一些資料,上面 NonConfigurationInstances 類中 viewModelStore 範例就是這樣儲存的

@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
    //取出自定義資料
    Object custom = onRetainCustomNonConfigurationInstance();
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // No one called getViewModelStore(), so see if there was an existing
        // ViewModelStore from our last NonConfigurationInstance
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }
    if (viewModelStore == null && custom == null) {
        return null;
    }
    //建立NonConfigurationInstances物件儲存 自定義資料和 viewModelStore 範例物件
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

我們看到 onRetainNonConfigurationInstance() 已被標記為 final,官方不建議我們自己再複寫此方法,而 onRetainCustomNonConfigurationInstance() 和與之對應的 getLastCustomNonConfigurationInstance()也都被標記為廢棄,可以看出官方還沒有提供成熟方案。

@Deprecated
@Nullable
public Object onRetainCustomNonConfigurationInstance() {
    return null;
}

@Deprecated
@Nullable
public Object getLastCustomNonConfigurationInstance() {
    NonConfigurationInstances nc = (NonConfigurationInstances)
            getLastNonConfigurationInstance();
    return nc != null ? nc.custom : null;
}
ViewModelStore.clear()

重新回到 ViewModelStore類的 clear()這裡,ViewModelStore類程式碼比較簡單,我們著重看一下 clear(),其實就是遍歷 HashMap,並呼叫ViewModel中的clear()

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

ViewModelclear()方法如下:

@MainThread
final void clear() {
    //設定標誌位
    mCleared = true;
   
   //清除快取的tag map集合
    if (mBagOfTags != null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                // see comment for the similar call in setTagIfAbsent
                closeWithRuntimeException(value);
            }
        }
    }
    //供子類使用清除子類資料
    onCleared();
}

我們看到 clear()一共幹了三件事:

  1. 設定清除標誌位
  2. 清除快取在HashMap中的tag資料
  3. 呼叫onCleared() 子類可以重寫此方法完成清除資料

解決InputMethodManager中 mNextServedView 持有Activity導致記憶體漏失

在Android 4.4~6.0之間一直存在一個比較常見的系統bug,那就是 InputMethodManager類中 mNextServedViewactivity銷燬後也會一直持有Activity參照從而導致記憶體漏失,使用LeakCanary很容易檢測出來

在這裡插入圖片描述

常見的解決方式是通過反射得到 InputMethodManagermNextServedViewActivity銷燬後置為null,把參照鏈給斷開 比如可以參考這篇文章傳統解決方式、下面我們看一下 ComponentActivity 是怎麼解決這個問題的

前面我們已經在構造器中看到如下程式碼:

if (19 <= SDK_INT && SDK_INT <= 23) {
    getLifecycle().addObserver(new ImmLeaksCleaner(this));
}

利用 Lifecyle新增一個觀察者物件,建立了一個 ImmLeaksCleaner並將當前 Activity物件傳入

@RequiresApi(19)
final class ImmLeaksCleaner implements LifecycleEventObserver {
    //變數初始化狀態列舉值
    private static final int NOT_INITIALIAZED = 0;
    private static final int INIT_SUCCESS = 1;
    private static final int INIT_FAILED = 2;
    
    //初始化狀態
    private static int sReflectedFieldsInitialized = NOT_INITIALIAZED;
    
    //反射對應的欄位值
    private static Field sHField;
    private static Field sServedViewField;
    private static Field sNextServedViewField;

    private Activity mActivity;

    ImmLeaksCleaner(Activity activity) {
        mActivity = activity;
    }

    @Override
    public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
        //activity生命週期走到 onDestory() 時才會往下執行
        if (event != Lifecycle.Event.ON_DESTROY) {
            return;
        }
        //發現沒有初始化進行初始化反射出指定欄位
        if (sReflectedFieldsInitialized == NOT_INITIALIAZED) {
            initializeReflectiveFields();
        }
        //反射成功
        if (sReflectedFieldsInitialized == INIT_SUCCESS) {
            //獲取當前InputMethodManager物件
            InputMethodManager inputMethodManager = (InputMethodManager)
                    mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
            //拿到當前lock鎖物件
            final Object lock;
            try {
                lock = sHField.get(inputMethodManager);
            } catch (IllegalAccessException e) {
                return;
            }
            if (lock == null) {
                return;
            }
            //進入同步鎖
            synchronized (lock) {
                final View servedView;
                try {
                    servedView = (View) sServedViewField.get(inputMethodManager);
                } catch (IllegalAccessException e) {
                    return;
                } catch (ClassCastException e) {
                    return;
                }
                if (servedView == null) {
                    return;
                }
                if (servedView.isAttachedToWindow()) {
                    return;
                }
                //將mNextServedView物件設定為null
                try {
                    sNextServedViewField.set(inputMethodManager, null);
                } catch (IllegalAccessException e) {
                    return;
                }
            }
            inputMethodManager.isActive();
        }
    }

    @MainThread
    private static void initializeReflectiveFields() {
        try {
            //設定標識位標識開始反射
            sReflectedFieldsInitialized = INIT_FAILED;
            sServedViewField = InputMethodManager.class.getDeclaredField("mServedView");
            sServedViewField.setAccessible(true);
            sNextServedViewField = InputMethodManager.class.getDeclaredField("mNextServedView");
            sNextServedViewField.setAccessible(true);
            //對應是Handler實現類
            sHField = InputMethodManager.class.getDeclaredField("mH");
            sHField.setAccessible(true);
            //反射成功重置標識位
            sReflectedFieldsInitialized = INIT_SUCCESS;
        } catch (NoSuchFieldException e) {
            // very oem much custom ¯\_(ツ)_/¯
        }
    }
}

以上就是 ImmLeaksCleaner類解決方式,程式碼比較簡單有詳細的註釋,就不再贅述了


對Lifecycle的支援

我們上面看到 ComponentActivity實現了 LifecycleOwner介面,內部建立了 LifecycleRegistry 物件並將當前Activity範例傳入

//建立 LifecycleRegistry 物件
private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);

getLifecycle()返回的值即 mLifecycleRegistry

@NonNull
@Override
public Lifecycle getLifecycle() {
    return mLifecycleRegistry;
}

getLifecycle()實現很簡單,就是將new出來的mLifecycleRegistry返回,我們從getLifecycle() 的註釋上我們提取到兩點資訊:

  • 官方不推薦重寫 getLifecycle() 而且會在未來高版本中會將此方法標記為 final
  • 如果你想重寫 getLifecycle() 就需要遵循以下兩條
    1. 必須返回一個 LifecycleRegistry 物件
    2. LifecycleRegistry 物件進行懶初始化
      注意:在LifecycleRegistry 物件初始化完成之前,這個物件將會在父類別的構造器中呼叫

對fragment返回鍵排程支援

什麼是對fragment返回鍵的排程支援? 其本質就是讓fragment 能像Activity一樣在按下返回鍵時能夠回撥onBackPressed() 所以BackPressedDispatcher排程器本質也是將onBackPressed()回撥到fragment裡面實現而已

下面我們先看一個在fragment裡面具體如何使用返回撥度?

使用BackPressedDispatcher

1.建立Activity

第一步建立一個測試Activity內部佈局和相關程式碼如下:

class BackMainActivity : BaseEasyActivity() {


    override fun getLayoutId(): Int {
        return R.layout.activity_jetpack_back_main
    }

    override fun initView() {

        supportFragmentManager.beginTransaction()
            .replace(R.id.backContent, BackListFragment())
            .commitNowAllowingStateLoss()

    }

    override fun onBackPressed() {
        super.onBackPressed()
        Logger.d("onBackPressed")
    }
}

activity_jetpack_back_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/backContent"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

BackListFragment:

class BackListFragment : BaseEasyListFragment() {


    override fun initView() {
        super.initView()

        requireActivity().onBackPressedDispatcher
            .addCallback(this, object : OnBackPressedCallback(true) {
                override fun handleOnBackPressed() {
                    Logger.i("back 1")
                    back()
                }
            })
        requireActivity().onBackPressedDispatcher.addCallback(this,object :OnBackPressedCallback(true){
            override fun handleOnBackPressed() {
                Logger.i("back 2")

                back2()
            }
        })


    }

    private fun back2() {
        activity?.let {
            AlertDialog.Builder(it).setTitle("EasyTitle 2")
                .setMessage("你確定退出嗎?")
                .setNegativeButton(
                    "取消"
                ) { dialog, _ ->
                    dialog?.dismiss()
                }
                .setPositiveButton(
                    "確定"
                ) { dialog, _ ->
                    dialog?.dismiss()
                    requireActivity().finish()
                }
                .create()
                .show()
        }

    }

    private fun back() {
        activity?.let {
            AlertDialog.Builder(it).setTitle("EasyTitle 1")
                .setMessage("你確定退出嗎?")
                .setNegativeButton(
                    "取消"
                ) { dialog, _ ->
                    dialog?.dismiss()
                }
                .setPositiveButton(
                    "確定"
                ) { dialog, _ ->
                    dialog?.dismiss()
                    requireActivity().finish()
                }
                .create()
                .show()
        }
    }
}

fragment裡面我們呼叫requireActivity().onBackPressedDispatcher.addCallback()新增了兩個回撥,並在handleOnBackPressed()回撥中我們彈出一個確認彈框

addCallback() 這個方法有兩個引數含義分別是:

  1. @NonNull LifecycleOwner owner : 當前的lifecycle實現物件 ActiivtyFragment頂級類都實現了LifecycleOwner介面,所以第一個引數一般傳入this就可以了
  2. @NonNull OnBackPressedCallback onBackPressedCallback : OnBackPressedCallback接收返回鍵回撥抽象類,子類需要繼承此類,其中構造方法中的 boolean enabled 引數必須傳入 true如果 傳入 false此回撥不會執行,預設值為 false

以上就是我們Demo全部程式碼了,當我們執行程式,點選返回鍵 我們看到 back2()裡的彈框顯示出來了,點選確定按鈕將會呼叫finish()關閉當前頁面

你可能會疑問我們註冊了兩個回撥,但是back()彈框並沒有顯示,是怎麼回事呢?那就只能看一下原始碼才能知道答案

返回撥度原始碼解析

返回鍵排程程式碼的源頭還是在 ComponentActivity中,讓我們重新將注意力轉移到此類中,前文中我們看到 ComponentActivity實現的介面有一個 OnBackPressedDispatcherOwner :

public interface OnBackPressedDispatcherOwner extends LifecycleOwner {

    /**
     * Retrieve the {@link OnBackPressedDispatcher} that should handle the system back button.
     *
     * @return The {@link OnBackPressedDispatcher}.
     */
    @NonNull
    OnBackPressedDispatcher getOnBackPressedDispatcher();
}

可以看到 OnBackPressedDispatcherOwner繼承與 LifecycleOwner 那麼他自然也擁有lifecycle相關的功能 getOnBackPressedDispatcher() 是返回一個返回鍵路由類,這個類會將系統返回鍵觸發路由到指定回撥中

下面我們看一下 ComponentActivity類返回鍵路由相關程式碼和實現邏輯

private final OnBackPressedDispatcher mOnBackPressedDispatcher =
        new OnBackPressedDispatcher(new Runnable() {
            @Override
            public void run() {
                ComponentActivity.super.onBackPressed();
            }
        });

@Override
@MainThread
public void onBackPressed() {
    mOnBackPressedDispatcher.onBackPressed();
}

@NonNull
@Override
public final OnBackPressedDispatcher getOnBackPressedDispatcher() {
    return mOnBackPressedDispatcher;
}        
  • ComponentActivity建立出返回鍵路由類。並傳入了一個預設任務,run()中並將此次點選返回鍵任務交由父類別來實現,這個預設邏輯只有在不存在任何自定義回撥的情況下執行
  • onBackPressed()中就是將任務交由 OnBackPressedDispatcher來執行
  • getOnBackPressedDispatcher()只是將當前建立出來的範例進行返回,不過這個方法被標記為 final
OnBackPressedCallback 回撥

下面看一下 OnBackPressedCallback的具體實現

public abstract class OnBackPressedCallback {
    
    private boolean mEnabled;
    
    //儲存Cancellable介面集合
    private CopyOnWriteArrayList<Cancellable> mCancellables = new CopyOnWriteArrayList<>();
    
    public OnBackPressedCallback(boolean enabled) {
        mEnabled = enabled;
    }
    
    @MainThread
    public final void setEnabled(boolean enabled) {
        mEnabled = enabled;
    }
    
    @MainThread
    public final boolean isEnabled() {
        return mEnabled;
    }
    
    //呼叫所有 Cancellable的cancel()函數
    @MainThread
    public final void remove() {
        for (Cancellable cancellable: mCancellables) {
            cancellable.cancel();
        }
    }
    
    //子類需要實現的返回鍵邏輯
    @MainThread
    public abstract void handleOnBackPressed();
    
    //新增和移除 Cancellable 的方法,主要是元件庫程式碼內部呼叫(包存取許可權)
    void addCancellable(@NonNull Cancellable cancellable) {
        mCancellables.add(cancellable);
    }
    void removeCancellable(@NonNull Cancellable cancellable) {
        mCancellables.remove(cancellable);
    }
}

上述OnBackPressedCallback 程式碼邏輯比較簡單,有比較詳細的註釋就不再贅述了

Cancellable

Cancellable 是一個元件庫程式碼內部(包存取許可權),取消介面定義如下:

interface Cancellable {

    /**
     * Cancel the subscription. This call should be idempotent, making it safe to
     * call multiple times.
     */
    void cancel();
}
OnBackPressedDispatcher 返回撥度路由

下面我們看一下 OnBackPressedDispatcher 類具體實現

public final class OnBackPressedDispatcher {

    //預設返回任務
    @Nullable
    private final Runnable mFallbackOnBackPressed;

    //返回鍵任務佇列
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final ArrayDeque<OnBackPressedCallback> mOnBackPressedCallbacks = new ArrayDeque<>();

    //預設、有參構造器
    public OnBackPressedDispatcher() {
        this(null);
    }
    public OnBackPressedDispatcher(@Nullable Runnable fallbackOnBackPressed) {
        mFallbackOnBackPressed = fallbackOnBackPressed;
    }

    //新增返回鍵任務
    @MainThread
    public void addCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
        //呼叫addCancellableCallback()下面方法,將返回任務包裝成可需要性質的任務,
        //子類可以呼叫 OnBackPressedCallback中remove() 將此任務移除掉
        addCancellableCallback(onBackPressedCallback);
    }
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    @MainThread
    @NonNull
    Cancellable addCancellableCallback(@NonNull OnBackPressedCallback onBackPressedCallback) {
        //新增到全域性集合中
        mOnBackPressedCallbacks.add(onBackPressedCallback);
        //將普通任務包裝成可需要性質的任務
        OnBackPressedCancellable cancellable = new OnBackPressedCancellable(onBackPressedCallback);
        onBackPressedCallback.addCancellable(cancellable);
        return cancellable;
    }
    
    //新增任務,並指定了lifecycle物件
    @SuppressLint("LambdaLast")
    @MainThread
    public void addCallback(@NonNull LifecycleOwner owner,
            @NonNull OnBackPressedCallback onBackPressedCallback) {
        Lifecycle lifecycle = owner.getLifecycle();
        //不能在 DESTROYED 狀態時註冊
        if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
            return;
        }
        //新增一個LifecycleOnBackPressedCancellable 具有生命週期觀察能力,可需要性質的任務
        onBackPressedCallback.addCancellable(
                new LifecycleOnBackPressedCancellable(lifecycle, onBackPressedCallback));
    }

    //判斷是否存在開啟的回撥任務
    @MainThread
    public boolean hasEnabledCallbacks() {
        Iterator<OnBackPressedCallback> iterator =
                mOnBackPressedCallbacks.descendingIterator();
        while (iterator.hasNext()) {
            if (iterator.next().isEnabled()) {
                return true;
            }
        }
        return false;
    }

    //ComponentActivity 類中onBackPressed()會代理到這個方法裡執行 
    @MainThread
    public void onBackPressed() {
        //倒序遍歷
        Iterator<OnBackPressedCallback> iterator =
                mOnBackPressedCallbacks.descendingIterator();
        while (iterator.hasNext()) {
            OnBackPressedCallback callback = iterator.next();
            //如果OnBackPressedCallback中mEnabled值為 true才會執行
            //且只會執行任務佇列中第一個任務,所以一個fragment如果新增多個任務,只會執行最後新增的任務
            if (callback.isEnabled()) {
                callback.handleOnBackPressed();
                return;
            }
        }
        //上述任務佇列中沒有找到可執行的自定義任務,則會將此次事件交給ComponentActivity來執行 
        if (mFallbackOnBackPressed != null) {
            mFallbackOnBackPressed.run();
        }
    }
    
    //對普通返回任務進行包裝成可取消性質的
    private class OnBackPressedCancellable implements Cancellable {
        private final OnBackPressedCallback mOnBackPressedCallback;
        OnBackPressedCancellable(OnBackPressedCallback onBackPressedCallback) {
            mOnBackPressedCallback = onBackPressedCallback;
        }

        @Override
        public void cancel() {
            //從佇列中移除和移除自身回撥
            mOnBackPressedCallbacks.remove(mOnBackPressedCallback);
            mOnBackPressedCallback.removeCancellable(this);
        }
    }

    //對指定Lifecycle實現類進行包裝,內部自動處理生命週期相關狀態
    private class LifecycleOnBackPressedCancellable implements LifecycleEventObserver,
            Cancellable {
        private final Lifecycle mLifecycle;
        private final OnBackPressedCallback mOnBackPressedCallback;

        @Nullable
        private Cancellable mCurrentCancellable;

        LifecycleOnBackPressedCancellable(@NonNull Lifecycle lifecycle,
                @NonNull OnBackPressedCallback onBackPressedCallback) {
            mLifecycle = lifecycle;
            mOnBackPressedCallback = onBackPressedCallback;
            //新增lifecycle監聽
            lifecycle.addObserver(this);
        }

        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_START) {
                //在fragment啟動的時候將任務新增進去,並將任務包裝成可需要的任務
                mCurrentCancellable = addCancellableCallback(mOnBackPressedCallback);
            } else if (event == Lifecycle.Event.ON_STOP) {
                
                //在fragment stop的時候取消任務
                if (mCurrentCancellable != null) {
                    mCurrentCancellable.cancel();
                }
            } else if (event == Lifecycle.Event.ON_DESTROY) {
                //fragment銷燬時將任務取消
                cancel();
            }
        }

        @Override
        public void cancel() {
            //移除lifecycle回撥
            mLifecycle.removeObserver(this);
            //移除回撥
            mOnBackPressedCallback.removeCancellable(this);
            if (mCurrentCancellable != null) {
                mCurrentCancellable.cancel();
                mCurrentCancellable = null;
            }
        }
    }
}

以上就是OnBackPressedDispatcher返回撥度路由類的全部程式碼,有詳細的註釋應該看明白。

OnBackPressedDispatcher 是實現返回撥度人主要類,內部處理了新增任務,移除任務,將任務路由到指定的 fragment中,這裡在新增任務時 推薦使用addCallback(@NonNull LifecycleOwner owner, @NonNull OnBackPressedCallback onBackPressedCallback) 來新增任務,這樣就能和 lifecycle關聯起來,內部已經處理了和fragment生命週期相關的邏輯了。

activity-ktx擴充套件庫功能

activity-ktx擴充套件庫是 Google 使用kotlin語言開發的輔助庫,同時支援了kotlin協程,對於使用jetpack庫很有幫助

activity-ktx擴充套件庫主要包含兩個kotlin檔案:

  • OnBackPressedDispatcherKt
  • ActivityViewModelLazyKt

OnBackPressedDispatcherKt

是專門對 OnBackPressedDispatcher類的一個擴充套件和包裝來看看具體怎麼做的

//繼承OnBackPressedCallback並對 OnBackPressedDispatcher的addCallback()進行擴充套件
fun OnBackPressedDispatcher.addCallback(
    owner: LifecycleOwner? = null,
    enabled: Boolean = true,
    onBackPressed: OnBackPressedCallback.() -> Unit
): OnBackPressedCallback {
    //內部實現類
    val callback = object : OnBackPressedCallback(enabled) {
        override fun handleOnBackPressed() {
            //執行傳過來的函數式方法
            onBackPressed()
        }
    }
    //對LifecycleOwner不同情況呼叫不同API
    if (owner != null) {
        addCallback(owner, callback)
    } else {
        addCallback(callback)
    }
    return callback
}

從上述原始碼中我們看到這個方法功能還是比較多的,再具體使用時就比較方便了

使用:

//新增返回回撥
requireActivity().onBackPressedDispatcher.addCallback(owner = this,enabled = true,{
    //...
})

當然根據kotlin具名函數的特點,也可以省略前兩個引數:

requireActivity().onBackPressedDispatcher.addCallback(onBackPressed = {
    //...
})

所以在使用上比之前的方式要簡單很多

ActivityViewModelLazyKt

這個擴充套件類是為了幫助我們方便的使用ViewModel類,想想一下我們是如何建立ViewModel

這裡先建立出來一個自定義ViewModel,看一下有多少種方式建立範例

class BackViewModel(application: Application) : AndroidViewModel(application) {}

ViewModelProviders 方式

viewModel = ViewModelProviders.of(this).get(BackViewModel::class.java)

使用ViewModelProviders呼叫of()並呼叫get()就能建立範例,很方便的,但是很不幸在後來的版本中Google 先是將 ViewModelProviders標記為過時,再後來就直接刪除了

所以Google推薦直接使用ViewModelProvider來建立範例,其實 ViewModelProvidersof()get()也是對 ViewModelProvider的簡單封裝

ViewModelProvider 方式

val viewModel = ViewModelProvider(
    this,
    ViewModelProvider.AndroidViewModelFactory.getInstance(application)
).get(BackViewModel::class.java)

看著是有點麻煩哈…不過程式碼邏輯還是很好懂的

viewModels() 方式

下面看看利用 ActivityViewModelLazyKt 擴充套件元件建立ViewModel範例

只需要呼叫 viewModels() 函數就可以了

val backViewModel = viewModels<BackViewModel> {
    ViewModelProvider.AndroidViewModelFactory.getInstance(application)
}

當然上面方式是有點麻煩,還需要傳入一個 lambda 表示式感覺還不好理解,不過使用預設的 ViewModelFactory 就比較簡單了

val viewModel: BackViewModel by viewModels()

或者這樣寫

val viewModel by viewModels<BackViewModel>()

不過意思是一樣的相對以上方式就簡單很多了,幾乎感受不到 ViewModelProviderAndroidViewModelFactory 等類的存在

接下來看一下viewModels()是如何實現的

viewModels()簡要原始碼分析

//實現了Lazy介面具備懶載入欄位的功能
//ComponentActivity類的擴充套件函數
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    //①
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }
    //②
    return ViewModelLazy(VM::class, {
        viewModelStore
    }, factoryPromise)
}

上述程式碼雖然簡短但是功能比較多,我們具體看一下

  1. 根據引數是否為null來選擇是使用自定義 ViewModelProviderFactory 還是 預設的 ViewModelProviderFactory , defaultViewModelProviderFactory 變數對應是 mDefaultFactory (SavedStateViewModelFactory型別)
  2. 根據引數構建一個 ViewModelLazy物件返回

注意: 原始碼中有如下注釋

This property can be accessed only after the Activity is attached to the Application,
and access prior to that will result in IllegalArgumentException.

這裡的意思是,如果使用擴充套件函數初始化的屬性只能在Actiivty新增了Application後才能存取,在此之前的存取將會丟擲IllegalArgumentException異常

我們知道在啟動Activity 會呼叫 Activityattach()Application上下文物件賦予Activity上,所以我們應該保證變數不能在 ActivityonCreate()之前呼叫就可以了

總結

本文主要以 jetpack-activity 元件為切入點分析了該元件的主要功能,並根據原始碼瞭解了內部實現原理。
簡單來說 jetpack-activity 元件有如下功能:

  • 解決一些Android碎片化適配問題
  • Lifecycle系列元件提供了支援
  • 提供了返回鍵路由,對Fragment處理返回鍵提供了支援

同時 ktx 擴充套件元件也是對jetpack-activity 元件庫的一個補充,在其他的元件庫中 ktx 更是比較重要。

參考

https://developer.android.com/jetpack

https://developer.android.com/jetpack/androidx/releases/activity

https://www.jianshu.com/p/f2aa4cf53abd