Android 语言切换时发生了什么,源码是怎么调用的,对我们app有何影响,我们该怎么做去避免发生异常

在做应用的时候经常会碰到多语言的适配,在我们切换语言时候,我们的app到底发生了什么呢?以及切换语言经常出现的错误问题,比如空指针,fragment的问题等等。对于怎么适配多语言今天不是重点,重点是语言切换后我们的app去做了什么让她的语言发生了变化。以下情况都是在我们打开页面之后切换语言的,也就是我们app在前台,然后切换语言。

(1)切换语言时代码调用流程

(2)切换语言时候最容易导致的问题

(1)切换语言时候代码调用的流程

语言切换时我们的Activity经历了 从生--到死--再到生 的过程,切换语言时系统会触发 android_util_Binder.cpp 调用 onTransact 触发 Binder 的 execTransact() 方法,然后回调IApplicationThread 等等,如下流程图:

其实切换语言影响最大的就是在我们的FragmentManager 到 Fragment 之间。接下来会详细分析也就是我们切换语言时候最容易导致的错误。

(2)切换语言最容易导致的错误

我们知道我们一般编写的界面大致分为两种情况

1. Activity + 单个fragment 组成的页面

        getSupportFragmentManager().beginTransaction()
                .replace(R.id.mood_light_container, new MyFragment()).commit();

这种Fragment 的加载方式我们应该很熟悉,很正常的加载方式,这种在切换语言时最容易出现什么问题呢?由上面源码流程图12步 开始:

在调用 activity 的 onCreate() 方法时候首先调用的是super.onCreate() 父类的方法,父类的流程调用之后回来才会走我们在activity 的 onCreate() 中写的内容:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        //先调用
        super.onCreate(savedInstanceState);
        //后调用
        if (BuildConfig.DEBUG) {
            Log.d(TAG, "onCreate");
        }

        setContentView(R.layout.activity_ambience);

        //我们的内容
        xxx
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.mood_light_container, new MoodLightFragment()).commit();
    }

但是恰恰在 super.onCreate() 中会发生一些故事,在父类FragmentActivity中会创建一个FragmentController 来控制整个fragment的生命周期

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

在FragmnetActivity 的onCreate() 方法中会调用  mFragments.dispatchCreate(); 然后一次调用到fragmentManager 的 moveToState() 方法,

    void moveToState(int newState, boolean always) {
     
        ....

        mCurState = newState;

        if (mActive != null) {

            // Must add them in the proper order. mActive fragments may be out of order
         ....

            startPendingDeferredFragments();

            if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) {
                mHost.onSupportInvalidateOptionsMenu();
                mNeedMenuInvalidate = false;
            }
        }
    }

在这方法中有一个非常重要的判断 mActive != null mActive是每一次 我们add的 fragment对象,如果我们在当前页面去切换语言也就是我们的 mActive 是不为空的。然后就会调用 startPendingDeferredFragments(); 一步步调用到 Fragment 的onCreate() 方法中然后 回调我们的子类的 MyFragment 的onCreate() 方法,如果我们在子类的方法中做了一些逻辑对象的初始化是在Activity 的onCreate() 中初始化的 这时候就会报 空指针 异常。因为在activity 的onCreate()中 super.onCreate() 走完才会走我们的逻辑。

2. Activity + 多个fragment + Viewpager

这种也就是我们适配多个页面滑动切换的fragment,一般代码我们会在Activity 的 onCreate()中这样写

        MyFragment myf = new MyFragment();
        List<Fragment> fragments = new ArrayList<>();
        fragments.add(myf);
        FragmentAdapter adapter = new FragmentAdapter(getSupportFragmentManager(), fragments);
        

这会有什么问题呢,首先我们从第一种知道,每次切换语言我们activity都会再重新走一遍,这时候我们又创建了一个新的fragment对象 重新add 到 fragments 集合中 传到 FragmentAdapter ,然后一次加载到我们的 FragmentStatePagerAdapter / PagerAdapter 父类中。但是在我们的 FragmentAdapter 中会调用 super(fm);

然后再调用 instantiateItem()方法

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        
        //上次添加的,缓存的fragment对象,如果没消毁,我们当前使用的就是这个
        if (mFragments.size() > position) {
            Fragment f = mFragments.get(position);
            if (f != null) {
                return f;
            }
        }

       ....

        //本次add进来的新的fragment对象
        Fragment fragment = getItem(position);
        ....
        return fragment;
    }

通过这段代码我们知道如果我们页面没有销毁,在当前页面去切换语言,整个流程走完之后,虽然我们在onCreate() 中重新创建了新的fragment对象并且加入到了 PagerAdapter 中,但是我们activity 里面使用的fragment 确是我们新创建的,并不是从 PagerAdapter 中返回的,虽然我们新创建了fragment我们只是 new 出来了一个 fragment 对象,并没有走fragment 的生命周期,也就是 我们当前fragmentManager 中并没有我们新创建的fragment对象。所以这时候如果我们触发了fragment里面的方法调用,就会报空指针异常。

所以我们不能每次都 new fragmnet 新对象add到PagerFragment 中,我们要从 fragmentManager中去get 。如

    MyFragment myFragment;
        List<Fragment> fragments = getSupportFragmentManager().getFragments();
        for (Fragment fragment : fragments) {
            if (fragment instanceof MyFragment) {
                myFragment = fragment;
                break;
            }
            myFragment = new MyFragment();
        }
        List<Fragment> fragments = new ArrayList<>();
        fragments.add(myFragment);
        FragmentAdapter adapter = new FragmentAdapter(getSupportFragmentManager(), fragments);

以上说的情况都是对在我们当前页面去切换语言的情况,如果点击返回建之后回到桌面 再切换语言,则不会出现这种问题,因为回到桌面切换语言再打开app这种情况是activity 和fragment 销毁之后重新创建的,和我们关掉app 在启动是一样的。容易出问题是在我们当前页面切换语言 RELAUNCHER 的情况。

发布了119 篇原创文章 · 获赞 140 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/WangRain1/article/details/90511724