最近发现当把xml中的标签替换为<androidx.fragment.app.FragmentContainerView>,然后在Activity的onCreate方法获取NavController,就会发生does not have a NavController set on xxxxx 的错误

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController:NavController = findNavController(R.id.fragmentContainerView)
}
但是当替换回标签后,就可以正常获取NavController对象,只不过会有一个黄色警告  
接下来分析源码,看看究竟为什么使用<androidx.fragment.app.FragmentContainerView>会报错 回到MainActivity,跟进findNavController(R.id.fragmentContainerView)方法
fun Activity.findNavController(@IdRes viewId: Int): NavController =
Navigation.findNavController(this, viewId)
一个kotlin的扩展方法,里面调用的是Navigation的findNavController方法,继续跟进
@NonNull
public static NavController findNavController(@NonNull Activity activity, @IdRes int viewId) {
View view = ActivityCompat.requireViewById(activity, viewId);
NavController navController = findViewNavController(view);
if (navController == null) {
throw new IllegalStateException("Activity " + activity
+ " does not have a NavController set on " + viewId);
}
return navController;
}
可以看到,错误是在这里抛出的,也就是当找不到NavController的时候,会抛出异常。进入findViewNavController(view)方法
@Nullable
private static NavController findViewNavController(@NonNull View view) {
while (view != null) {
NavController controller = getViewNavController(view);
if (controller != null) {
return controller;
}
ViewParent parent = view.getParent();
view = parent instanceof View ? (View) parent : null;
}
return null;
}
这里是一个循环,就是当在当前view找不到的时候,会跑到父view去找。如果找到,直接返回NavController,没有找到,继续循环,直到循环到没有父view,才停止。 我们继续跟进getViewNavController(view);方法
@Nullable
private static NavController getViewNavController(@NonNull View view) {
Object tag = view.getTag(R.id.nav_controller_view_tag);
NavController controller = null;
if (tag instanceof WeakReference) {
controller = ((WeakReference<NavController>) tag).get();
} else if (tag instanceof NavController) {
controller = (NavController) tag;
}
return controller;
}
这里会通过view的getTag()方法来拿tag,强转NavController后返回。到这里可以知道,这个NavController对象是存放在view的tag里面的。 回到原来的问题,之所以会报错,是因为没有找到NavController,而我们传入的view是不可能为空的,那只有一种可能,就是NavController没有设置到view的tag里面,所以才导致找不到。
那么这个controller是什么时候设置到view的tag里面的呢? 查看Navigation类的源码,会发现下面这个方法
public static void setViewNavController(@NonNull View view,
@Nullable NavController controller) {
view.setTag(R.id.nav_controller_view_tag, controller);
}
可以猜测,应该是调用这个方法设置的 打个断点,查看一下 先注释掉MainActivity的代码,防止报错 
 debug运行  通过堆栈信息可以发现,是从Activity的onStart开始,最后调用到NavHostFragment的onViewCreated方法。 先来看NavHostFragment的onViewCreated方法
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
...
Navigation.setViewNavController(view, mNavController);
...
}
而这个onViewCreated方法,是从Activity的onStart调下来的,现在,已经找到解决问题的答案了。 在onCreate的时候,还没有调用的这个方法,所以当我们获取NavController的时候,肯定是获取不到的,因为NavController是在onStart才开始设置,所以,我们只需要在onStart执行完后再获取就不会报错的。
override fun onStart() {
super.onStart()
val navController:NavController = findNavController(R.id.fragmentContainerView)
}
但我就是想在onCreate中获取,有没有什么办法呢? 办法还是有的。 查看NavHostFragment的onCreate方法
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = requireContext();
mNavController = new NavHostController(context);
...
}
NavHostController是在这里创建的,那么我们只需要通过NavHostFragment来拿就可以啦,而NavHostFragment是一个Fragment,所以通过FragmentManager对象又可以拿到NavHostFragment。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
val navController:NavController = navHostFragment.navController
}
那如果我就想在onCreate中通过原有的findNavController()拿NavController可不可以呢? 答案是肯定的,我们回到之前的debug页面,可以看到是从onStart方法开始调,最后才设置了NavController,看一下onStart源码
AppCompatActivity的onStart
@Override
protected void onStart() {
super.onStart();
getDelegate().onStart();
}
跟进super.onStart();
@Override
protected void onStart() {
super.onStart();
mStopped = false;
if (!mCreated) {
mCreated = true;
mFragments.dispatchActivityCreated();
}
...
}
NavController 就是从这句mFragments.dispatchActivityCreated();一直往下,最后到NavHostFragment的onViewCreated设置的,所以我么只需要在onCreate中手动调一下这个dispatchActivityCreated();方法即可
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
mFragments这个成员变量声明在FragmentActivity中,而且权限是默认的,所以我们无法在MainActivity中直接调用。既然如此,只能用反射啦。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initNavController()
val navController: NavController = findNavController(R.id.fragmentContainerView)
}
fun initNavController() {
val mFragmentsField = FragmentActivity::class.java.getDeclaredField("mFragments").apply {
isAccessible = true
}
val mCreatedField = FragmentActivity::class.java.getDeclaredField("mCreated").apply {
isAccessible = true
}
val dispatchActivityCreatedMethod =
FragmentController::class.java.getDeclaredMethod("dispatchActivityCreated").apply {
isAccessible = true
}
dispatchActivityCreatedMethod.invoke(mFragmentsField.get(this))
mCreatedField.set(this, true)
}
}
最后,可以愉快的用原有的findNavController()啦。
|