Android Jetpack
已經(jīng)出來很久了,目前在自己的 開源項(xiàng)目 中體驗(yàn)了一把,不得不說很舒服,除了有一些坑之外,這次主要講解下 Jetpack
中的 Navigation
,Navigation
主要用來管理 Fragment
,方便實(shí)現(xiàn)單個(gè) Activity
及 N 多個(gè) Fragment
的 App
,Navigation
的使用網(wǎng)上一搜一大把,這里主要通過源碼,分析下 Navigation
是如何實(shí)現(xiàn) Fragment
的管理
從布局入手
Navigation
通過指定布局中的 fragment
即可實(shí)現(xiàn),即
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- name 指定了根 Fragment,defaultNavHost 用于設(shè)置 Fragment 控制系統(tǒng)返回鍵,
navGraph 用于指定 fragment 管理 graph -->
<fragment
android:id="@+id/nav_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/demo_navigation" />
</FrameLayout>
所以我們就從 NavHostFragment
這個(gè)類開始入手
NavHostFragment && NavHost
public class NavHostFragment extends Fragment implements NavHost {}
該 Fragment
實(shí)現(xiàn)了 NavHost
接口,這邊先跳開下,看下這個(gè)接口需要實(shí)現(xiàn)的方法
/**
* A host is a single context or container for navigation via a {@link NavController}.
*/
public interface NavHost {
/**
* Returns the {@link NavController navigation controller} for this navigation host.
*
* @return this host's navigation controller
*/
@NonNull
NavController getNavController();
}
看下官方給該接口的定位,「是個(gè) NavController
的宿主」,NavController
是啥,我們后面再來看,回到 NavHostFragment
,首先看下用于 Fragment
初始化常用的幾個(gè)方法 onInflate
,onAttach
,onViewCreated
,onCreateView
以及 onCreate
onInflate
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
@Nullable Bundle savedInstanceState) {
// 省略一些非關(guān)鍵代碼...
// 映射布局的 navGraph 屬性,并賦值給 mGraphId,該值用于指定導(dǎo)航圖
final int graphId = navHost.getResourceId(R.styleable.NavHost_navGraph, 0);
if (graphId != 0) {
mGraphId = graphId;
}
// 省略一些非關(guān)鍵代碼...
// 映射布局的 defaultNavHost 并賦值給 mDefaultNavHost,該值用于設(shè)置是否將返回鍵控制權(quán)給 fragment
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
if (defaultHost) {
mDefaultNavHost = true;
}
}
onAttach
@CallSuper
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
// 如果設(shè)置獲取返回鍵控制權(quán)的屬性為 true,通過 setPrimaryNavigationFragment 方法進(jìn)行設(shè)置
// 否則,控制權(quán)還是在 activity
if (mDefaultNavHost) {
requireFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
}
onViewCreated
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 該方法通過設(shè)置 view 的 tag 屬性為 controller,后期獲取 controller 可能會(huì)使用,下同
Navigation.setViewNavController(view, mNavController);
if (view.getParent() != null) {
View rootView = (View) view.getParent();
if (rootView.getId() == getId()) {
Navigation.setViewNavController(rootView, mNavController);
}
}
}
onCreateView
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// FragmentContainerView 實(shí)際是一個(gè) FrameLayout,在該生命周期中,將 fragment 的 id 設(shè)置給父布局
FragmentContainerView containerView = new FragmentContainerView(inflater.getContext());
containerView.setId(getId());
return containerView;
}
onCreate
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = requireContext();
// 初始化 NavController 的一些屬性,并將 controller 設(shè)置給宿主
// 包括關(guān)聯(lián) lifeCycler,返回鍵的監(jiān)聽屬性等
mNavController = new NavHostController(context);
// ... 省略一些屬性設(shè)置代碼
// 在 onCreateNavController 方法中,給 controller 中的 NavigatorProvider 添加了
// DialogFragmentNavigator 和 FragmentNavigator,這兩個(gè)類具體實(shí)現(xiàn)了什么,先留點(diǎn)懸念,稍后解讀
onCreateNavController(mNavController);
// 獲取 store 的狀態(tài),并判斷是否要獲取返回鍵控制
Bundle navState = null;
if (savedInstanceState != null) {
navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
mDefaultNavHost = true;
requireFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
}
// 將保存的狀態(tài)設(shè)置回去
if (navState != null) {
mNavController.restoreState(navState);
}
// 將映射的 navigation 布局設(shè)置給 controller
if (mGraphId != 0) {
// Set from onInflate()
mNavController.setGraph(mGraphId);
} else {
// See if it was set by NavHostFragment.create()
final Bundle args = getArguments();
final int graphId = args != null ? args.getInt(KEY_GRAPH_ID) : 0;
final Bundle startDestinationArgs = args != null
? args.getBundle(KEY_START_DESTINATION_ARGS)
: null;
if (graphId != 0) {
mNavController.setGraph(graphId, startDestinationArgs);
}
}
}
通過上述的幾個(gè)方法,將 NavController
,defaultNavHost
,NavGraph
的值初始化完成,在 NavHostFragment
中還有個(gè)非常重要的方法 findNavController
,通過該方法,可以獲取到 Fragment
的管理者 NavController
findNavController
@NonNull
public static NavController findNavController(@NonNull Fragment fragment) {
Fragment findFragment = fragment;
while (findFragment != null) {
// 如果當(dāng)前傳入的 fragment 就是 NavHostFragment 則直接返回 onCreate 中初始化的 mNavController
if (findFragment instanceof NavHostFragment) {
return ((NavHostFragment) findFragment).getNavController();
}
// 如果不是則通過 onAttach / onCreate 方法中通過 setPrimaryNavigationFragment 方法
// 設(shè)置的 fragment 并返回 mNavController
Fragment primaryNavFragment = findFragment.requireFragmentManager()
.getPrimaryNavigationFragment();
if (primaryNavFragment instanceof NavHostFragment) {
return ((NavHostFragment) primaryNavFragment).getNavController();
}
// 如果上述都不成立,則獲取父級(jí)的 Fragment,繼續(xù)循環(huán)去判斷獲取
findFragment = findFragment.getParentFragment();
}
// Try looking for one associated with the view instead, if applicable
View view = fragment.getView();
if (view != null) {
return Navigation.findNavController(view);
}
throw new IllegalStateException("Fragment " + fragment
+ " does not have a NavController set");
}
所以,當(dāng)我們封裝 Fragment
基類的時(shí)候,即可通過該方法,為所有的 Fragment
尋找其對(duì)應(yīng)的 NavController
在介紹 NavHostFragment
的時(shí)候,有個(gè)類 NavController
也出現(xiàn)了多次,該 Fragment
就是其宿主,接著就看下 Controller
里面做了什么操作
NavController
NavController
作為整個(gè) App
的 Fragment
管理者,有幾個(gè)比較重要的方法,包括 SetGraph
設(shè)置「導(dǎo)航圖」,navigate
跳轉(zhuǎn) fragment
界面,navigateUp
返回回退棧上個(gè)界面,getNavInflater
用于映射 navigation.xml
文件
setGraph
setGraph
重載的方法比較多,但最終會(huì)調(diào)用 onGraphCreated
方法
private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
// 獲取之前保存的狀態(tài),并設(shè)置狀態(tài)至 Navigator,Navgator 通過 name 存在 NavigatorProvider 中
// 在 NavigatorProvider 中有個(gè) HashMap 用來存儲(chǔ) Navigator
if (mNavigatorStateToRestore != null) {
ArrayList<String> navigatorNames = mNavigatorStateToRestore.getStringArrayList(
KEY_NAVIGATOR_STATE_NAMES);
if (navigatorNames != null) {
for (String name : navigatorNames) {
Navigator<?> navigator = mNavigatorProvider.getNavigator(name);
Bundle bundle = mNavigatorStateToRestore.getBundle(name);
if (bundle != null) {
navigator.onRestoreState(bundle);
}
}
}
}
if (mBackStackToRestore != null) {
for (Parcelable parcelable : mBackStackToRestore) {
// ... 省略一些獲取屬性的代碼
// ... 設(shè)置屬性并壓入回退棧
NavBackStackEntry entry = new NavBackStackEntry(mContext, node, args,
mLifecycleOwner, mViewModel,
state.getUUID(), state.getSavedState());
mBackStack.add(entry);
}
// 更新當(dāng)前是否可以獲取系統(tǒng)返回按鈕的控制權(quán)
updateOnBackPressedCallbackEnabled();
mBackStackToRestore = null;
}
// 當(dāng)設(shè)置完「導(dǎo)航圖」后,判斷是否有 deepLink 屬性,如果沒有則顯示第一個(gè)界面
// deepLink 用于設(shè)置 url,可直接跳轉(zhuǎn)指定的界面
// 例如,當(dāng)收到通知后需要跳轉(zhuǎn)指定界面,則可以通過 deepLink 實(shí)現(xiàn)
if (mGraph != null && mBackStack.isEmpty()) {
boolean deepLinked = !mDeepLinkHandled && mActivity != null
&& handleDeepLink(mActivity.getIntent());
if (!deepLinked) {
// Navigate to the first destination in the graph
// if we haven't deep linked to a destination
navigate(mGraph, startDestinationArgs, null, null);
}
}
}
navigate
navigate
用于跳轉(zhuǎn)界面,重載的方法也較多,最終調(diào)用的內(nèi)部私有方法 navigate
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
boolean popped = false;
// navOptions 用于設(shè)置跳轉(zhuǎn)的動(dòng)畫,pop 時(shí)候?qū)?yīng)的界面等,具體可以查看 NavOptions 類
if (navOptions != null) {
if (navOptions.getPopUpTo() != -1) {
popped = popBackStackInternal(navOptions.getPopUpTo(),
navOptions.isPopUpToInclusive());
}
}
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
// 實(shí)際通過 Navigator.navigate 進(jìn)行跳轉(zhuǎn)
// Navigator 是個(gè)抽象類,具體實(shí)現(xiàn)類有 ActivityNavigator,F(xiàn)ragmentNavigator,
// DialogFragmentNavigator,NavGraphNavigator,NoOpNavigator等,且在類頭部使用了 Name 注解,
// 通過 Name 注解,能夠在 NavigatorProvider 注冊(cè)相應(yīng)的 Navigator
// 在 navigation.xml 布局中,通過 Name 對(duì)應(yīng)的值,進(jìn)行注冊(cè)即可,
// 例如注冊(cè) fragment 則直接使用 <fragment></fragment> 標(biāo)簽,
// 同時(shí)還有 <activity></activity>,<dialog></dialog>,<navigation></navigation> 等標(biāo)簽
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
if (newDest != null) {
if (!(newDest instanceof FloatingWindow)) {
// 如果跳轉(zhuǎn)的界面不是 FloatingWindow 則持續(xù)通過 popBackStackInternal 出棧,一直到滿足條件
while (!mBackStack.isEmpty()
&& mBackStack.peekLast().getDestination() instanceof FloatingWindow
&& popBackStackInternal(
mBackStack.peekLast().getDestination().getId(), true)) {
// Keep popping
}
}
// ... 省略入棧部分,當(dāng)跳轉(zhuǎn)完成后,則通知監(jiān)聽
if (popped || newDest != null) {
dispatchOnDestinationChanged();
}
}
navigateUp
navigateUp
用于回退上個(gè)界面,當(dāng)調(diào)用該方法時(shí),會(huì)通過回退棧中的數(shù)量進(jìn)行不同處理,如果數(shù)量為 1 則會(huì)直接 finish
對(duì)應(yīng)的 activity
,否則調(diào)用 popBackStack
方法,而 popBackStack
最終會(huì)調(diào)用 popBackStackInternal
方法,該方法返回一個(gè) Boolean
值,用于判斷是否出棧成功
boolean popBackStackInternal(@IdRes int destinationId, boolean inclusive) {
// ...
// 列表用于存儲(chǔ)需要出棧的 Navigator
ArrayList<Navigator<?>> popOperations = new ArrayList<>();
Iterator<NavBackStackEntry> iterator = mBackStack.descendingIterator();
boolean foundDestination = false;
// 遍歷回退棧的,并將符合出棧條件的 Navigator 放入列表
// 如果已經(jīng)找到了需要的 destination 則打斷循環(huán)
while (iterator.hasNext()) {
NavDestination destination = iterator.next().getDestination();
Navigator<?> navigator = mNavigatorProvider.getNavigator(
destination.getNavigatorName());
if (inclusive || destination.getId() != destinationId) {
popOperations.add(navigator);
}
if (destination.getId() == destinationId) {
foundDestination = true;
break;
}
}
//...對(duì)需要出棧的進(jìn)行出棧處理
return popped;
}
getNavInflater
getNavInflater
通過將 mNavigatorProvider
傳給 NavInflater
,前面提到過,NavigatorProvider
是用來保存一系列的 Navigator
,那么當(dāng)傳入到 NavInflater
中后,該類會(huì)對(duì)包含的 Navigator
進(jìn)行解析成一個(gè)個(gè) Destination
,用于導(dǎo)航跳轉(zhuǎn),具體如何解析的有興趣的朋友可以自己看
在上面的 navigate
方法中,提到了實(shí)際跳轉(zhuǎn)是通過 Navigator #navigate
進(jìn)行跳轉(zhuǎn)的,但是 Navigator
是個(gè)抽象類,具體的實(shí)現(xiàn)由子類完成,因?yàn)楦嗟臅?huì)使用 fragment
,所以我們只看下 FragmentNavigator
類下的 navigate
方法
FragmentNavigator
navigate
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
// ...
// 通過 destination 的 className 尋找相應(yīng)的 Fragment,并設(shè)置一些傳遞的參數(shù)
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
// ...設(shè)置一些動(dòng)畫等屬性
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
// 根據(jù)是否是 singleTop,做不同的入棧處理
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
// ...設(shè)置一些共享元素
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
NavAction && NavDestination
除了上述的幾個(gè)類以外,Navigation
還有比較重要的就是 NavAction
和 NavDestination
,NavAction
中指定了跳轉(zhuǎn)的 DestinationId
,額外的攜帶參數(shù)等,可以簡(jiǎn)單的看成一個(gè)實(shí)體類,NavDestination
中則包含了各種 NavAction
,DeepLink
等多種屬性,構(gòu)成了「導(dǎo)航圖」上的一個(gè)個(gè)點(diǎn)。
解決重新創(chuàng)建 Fragment
的坑
Navigation
目前比較大的一個(gè)坑就是存在 Fragment
在重新回到界面上的時(shí)候會(huì)重新創(chuàng)建,既然是坑,那就得解決啊,這邊我們借助 ViewModel + LiveData
來完成,封裝一個(gè)基類
abstract class BaseFragment<VB : ViewDataBinding> : Fragment() {
protected var mBinding: VB? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
retainInstance = true
// 保證只會(huì)創(chuàng)建一次 view,然后通過 ViewModel + LiveData 對(duì) view 顯示內(nèi)容進(jìn)行控制
if (mBinding == null) {
mBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false)
actionsOnViewInflate()
}
return mBinding?.root
}
// 該方法完整走完一個(gè)生命周期只會(huì)走一次,可用于該頁面進(jìn)入時(shí)網(wǎng)絡(luò)請(qǐng)求
open fun actionsOnViewInflate() {}
abstract fun getLayoutId(): Int
}
但是按照這么封裝,在使用 ViewPager + Fragment
的時(shí)候會(huì)出現(xiàn)重復(fù)添加的問題,再做下修改,將添加的先從父布局移除,再添加,就可以完美解決 Navigation
留下的坑
abstract class BaseFragment<VB : ViewDataBinding> : Fragment() {
protected var mBinding: VB? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
retainInstance = true
if (mBinding == null) {
mBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false)
actionsOnViewInflate()
}
// 解決 ViewPager + Fragment 情況下重復(fù)添加的問題
return if (mBinding != null) {
mBinding!!.root.apply { (parent as? ViewGroup)?.removeView(this) }
} else super.onCreateView(inflater, container, savedInstanceState)
}
}
一張圖總結(jié)
看了那么多源碼,最后用一張比較形象的圖來結(jié)束吧