jetpack分析4:ViewModel分析

83 阅读4分钟

问题思考

  • 为啥要用viewmodel呢?

1. viewmodel有啥用

   用大白话说,保证数据稳定性,以下面例子举例

class MainActivity : AppCompatActivity() {

    private val Tag = "jetpack-->"

    private val mVm = MainVm()
//    private val mVm: MainVm by lazy {
//        ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(MainVm::class.java)
//    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(Tag, "onCreate: ")
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        binding.mainVm = mVm
    }

    fun onClick(view: View) {
        mVm.count.set((mVm.count.get() ?: 0) + 1)
    }

    override fun onRetainCustomNonConfigurationInstance(): Any? {
        Log.d(Tag, "onRetainCustomNonConfigurationInstance: ")
        return super.onRetainCustomNonConfigurationInstance()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        Log.d(Tag, "onSaveInstanceState(outState)")
    }


    override fun onDestroy() {
        super.onDestroy()
        Log.d(Tag, "onDestroy: ")
    }
}

class MainVm: ViewModel() {
 var count = ObservableField(0)

}

// xml 布局
 <Button
    android:id="@+id/btn_count"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="onClick"
    android:text="@{mainVm.count+``}" />

   上面是一个页面只有一个button的布局,如果我们mVm是直接new出来,那么他失去了viewmodel原本作用,官方写法是通过ViewModelProvider创建。以上例子我们不断点击按钮会自增,当旋转屏幕的时候,原来Activity 会被销毁,并重新创建,同时mainVm.count也会重置=0。

   当我们用官方提供的写法,使用ViewModelProvider创建viewmodel,那么屏幕旋转的时候也是会保持mainVm.count原有数据

// 使用val mVm = MainVm() 的旋转屏幕日志
2023-02-20 14:34:17.762 28617-28617/com.wqg.wqgdatabdingandviewmodel D/jetpack-->: onSaveInstanceState(outState)   mVm.count=5
2023-02-20 14:34:17.762 28617-28617/com.wqg.wqgdatabdingandviewmodel D/jetpack-->: onRetainCustomNonConfigurationInstance:  mVm.count=5
2023-02-20 14:34:17.763 28617-28617/com.wqg.wqgdatabdingandviewmodel D/jetpack-->: onDestroy: 
2023-02-20 14:34:17.785 28617-28617/com.wqg.wqgdatabdingandviewmodel D/jetpack-->: onCreate:   mVm.count=0

   使用new 生成的viewmodel,onCreate 的时候会重新新建对象

// 使用官方ViewModelProvider创建MainVM 旋转屏幕日志
2023-02-20 14:35:26.263 29036-29036/com.wqg.wqgdatabdingandviewmodel D/jetpack-->: onSaveInstanceState(outState)   mVm.count=6
2023-02-20 14:35:26.263 29036-29036/com.wqg.wqgdatabdingandviewmodel D/jetpack-->: onRetainCustomNonConfigurationInstance:  mVm.count=6
2023-02-20 14:35:26.265 29036-29036/com.wqg.wqgdatabdingandviewmodel D/jetpack-->: onDestroy: 
2023-02-20 14:35:26.286 29036-29036/com.wqg.wqgdatabdingandviewmodel D/jetpack-->: onCreate:   mVm.count=6

  使用viewModelProvider创建的viewmodel, onCreate 的时候还是原来数据

2. viewmodel它是怎么实现这个

   可以看到我们切换屏幕是会调用onRetainCustomNonConfigurationInstance这个方法,直接中文式翻译『保留自定义非配置实例』,反查可以看到Activity的onRetainNonConfigurationInstance调用该方法,来看看

Activity.java
public final Object onRetainNonConfigurationInstance() {
    // 自定义的非实例
    Object custom = onRetainCustomNonConfigurationInstance();

    // 系统默认的
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // 可以看到,会获取最近的『非配置实例』,nc
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // 从nc 获取这个viewModelStore
            viewModelStore = nc.viewModelStore;
        }
    }

    if (viewModelStore == null && custom == null) {
        return null;
    }
    
    // 系统的viewModelStore和自定义的都存放到『非配置实例』里面
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

  以上是说明了我们自定义的实例对象和系统的viewModelStore存放到了nc里面了。怎么触发这个鬼东西呢?主要还是要看我们页面重启,那我们再从ActivityThread看看我们怎么调用onRetainNonConfigurationInstance。    横竖屏会调用ActivityThread的handleRelaunchActivity(),看名字你也能发现是重启,里面又有一个handleRelaunchActivityInner()

ActivityThread.java

private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
    // Preserve last used intent, it may be set from Activity#setIntent().
    final Intent customIntent = r.activity.mIntent;
    // Need to ensure state is saved.
    if (!r.paused) {
        // 进入了pause了
        performPauseActivity(r, false, reason, null /* pendingActions */);
    }
    if (!r.stopped) {
        // 进入了onStop
        callActivityOnStop(r, true /* saveState */, reason);
    }

    // 重点看这个,既然要销毁,那再销毁之前肯定要保存
    handleDestroyActivity(r, false, configChanges, true, reason);

    r.activity = null;
    r.window = null;
    r.hideForNow = false;
    r.nextIdle = null;
    // Merge any pending results and pending intents; don't just replace them
    if (pendingResults != null) {
        if (r.pendingResults == null) {
            r.pendingResults = pendingResults;
        } else {
            r.pendingResults.addAll(pendingResults);
        }
    }
    if (pendingIntents != null) {
        if (r.pendingIntents == null) {
            r.pendingIntents = pendingIntents;
        } else {
            r.pendingIntents.addAll(pendingIntents);
        }
    }
    r.startsNotResumed = startsNotResumed;
    r.overrideConfig = overrideConfig;
    // 启动activity
    handleLaunchActivity(r, pendingActions, customIntent);
}


@Override
public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
        boolean getNonConfigInstance, String reason) {
    // 核心代码
    performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
    cleanUpPendingRemoveWindows(r, finishing);
    WindowManager wm = r.activity.getWindowManager();
    View v = r.activity.mDecor;
    if (v != null) {
        if (r.activity.mVisibleFromServer) {
            mNumVisibleActivities--;
        }
        IBinder wtoken = v.getWindowToken();
        if (r.activity.mWindowAdded) {
            if (r.mPreserveWindow) {
                r.mPendingRemoveWindow = r.window;
                r.mPendingRemoveWindowManager = wm;
                
                r.window.clearContentView();
            } else {
                final ViewRootImpl viewRoot = v.getViewRootImpl();
                if (viewRoot != null) {
                    viewRoot.setActivityConfigCallback(null);
                }
                wm.removeViewImmediate(v);
            }
        }
        if (wtoken != null && r.mPendingRemoveWindow == null) {
            WindowManagerGlobal.getInstance().closeAll(wtoken,
                    r.activity.getClass().getName(), "Activity");
        } else if (r.mPendingRemoveWindow != null) {
            WindowManagerGlobal.getInstance().closeAllExceptView(r.token, v,
                    r.activity.getClass().getName(), "Activity");
        }
        r.activity.mDecor = null;
    }
    if (r.mPendingRemoveWindow == null) {
        WindowManagerGlobal.getInstance().closeAll(r.token,
                r.activity.getClass().getName(), "Activity");
    }

    Context c = r.activity.getBaseContext();
    if (c instanceof ContextImpl) {
        ((ContextImpl) c).scheduleFinalCleanup(r.activity.getClass().getName(), "Activity");
    }
    if (finishing) {
        ActivityClient.getInstance().activityDestroyed(r.token);
    }
    mSomeActivitiesChanged = true;
}

void performDestroyActivity(ActivityClientRecord r, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {
    Class<? extends Activity> activityClass = null;
    if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
    activityClass = r.activity.getClass();
    r.activity.mConfigChangeFlags |= configChanges;
    if (finishing) {
        r.activity.mFinished = true;
    }

    performPauseActivityIfNeeded(r, "destroy");

    if (!r.stopped) {
        callActivityOnStop(r, false /* saveState */, "destroy");
    }
    if (getNonConfigInstance) {
        try {
            // 它来了它来了
            r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException("Unable to retain activity "
                        + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
            }
        }
    }
    try {
        r.activity.mCalled = false;
        mInstrumentation.callActivityOnDestroy(r.activity);
        if (!r.activity.mCalled) {
            throw new SuperNotCalledException("Activity " + safeToComponentShortString(r.intent)
                    + " did not call through to super.onDestroy()");
        }
        if (r.window != null) {
            r.window.closeAllPanels();
        }
    } catch (SuperNotCalledException e) {
        throw e;
    } catch (Exception e) {
        if (!mInstrumentation.onException(r.activity, e)) {
            throw new RuntimeException("Unable to destroy activity "
                    + safeToComponentShortString(r.intent) + ": " + e.toString(), e);
        }
    }
    r.setState(ON_DESTROY);
    mLastReportedWindowingMode.remove(r.activity.getActivityToken());
    schedulePurgeIdler();
    synchronized (this) {
        if (mSplashScreenGlobal != null) {
            mSplashScreenGlobal.tokenDestroyed(r.token);
        }
    }
    // updatePendingActivityConfiguration() reads from mActivities to update
    // ActivityClientRecord which runs in a different thread. Protect modifications to
    // mActivities to avoid race.
    synchronized (mResourcesManager) {
        mActivities.remove(r.token);
    }
    StrictMode.decrementExpectedActivityCount(activityClass);
}

   可以从上面看到handleRelaunchActivityInner->handleDestroyActivity->performDestroyActivity->r.activity.retainNonConfigurationInstances();。终于找到了。直白说就是销毁的时候会想办法把viewmodel存放到『自定义非配置实例』,新建的activity的时候直接从上一次存放的『自定义非配置实例』取对应的内容。