IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android Navigation 报错does not have a NavController set on xxxxx 解决方案 -> 正文阅读

[移动开发]Android Navigation 报错does not have a NavController set on xxxxx 解决方案

最近发现当把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) {
    	//根据传入的viewId找到对应的View,所以view是不会为空的
        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) {
        ...
        //在这里把mNavController设置到view的tag
        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();
		//在这里创建的NavHostController对象
        mNavController = new NavHostController(context);
      
      ...
      
    }

NavHostController是在这里创建的,那么我们只需要通过NavHostFragment来拿就可以啦,而NavHostFragment是一个Fragment,所以通过FragmentManager对象又可以拿到NavHostFragment。

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //1、先拿NavHostFragment
        val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment
        //2、再拿NavController
        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() {
        //获取mFragments成员变量
        val mFragmentsField = FragmentActivity::class.java.getDeclaredField("mFragments").apply {
            isAccessible = true
        }
        //获取mCreated成员变量
        val mCreatedField = FragmentActivity::class.java.getDeclaredField("mCreated").apply {
            isAccessible = true
        }
        //获取dispatchActivityCreated方法
        val dispatchActivityCreatedMethod =
            FragmentController::class.java.getDeclaredMethod("dispatchActivityCreated").apply {
                isAccessible = true
            }
        //调用dispatchActivityCreated方法
        dispatchActivityCreatedMethod.invoke(mFragmentsField.get(this))

        //别忘了把mCreated设置为true,防止dispatchActivityCreated在onStart中再次调用
        mCreatedField.set(this, true)
    }
}

最后,可以愉快的用原有的findNavController()啦。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-23 10:54:40  更:2021-07-23 10:56:11 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/29 1:29:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码