前言
在大多數(shù)車載系統(tǒng)應(yīng)用架構(gòu)中莽鸿,一個完整的應(yīng)用往往會包含三層:
HMI
Human Machine Interface,顯示UI信息,進(jìn)行人機(jī)交互。Service
在系統(tǒng)后臺進(jìn)行數(shù)據(jù)處理扮惦,監(jiān)控數(shù)據(jù)狀態(tài)。SDK
根據(jù)業(yè)務(wù)邏輯Service
對外暴露的通信接口亲桦,其他模塊通過它來完成IPC通信崖蜜。
當(dāng)然并不是所有的應(yīng)用都需要Service
,只有不能長久的駐留在內(nèi)存中客峭,且需要監(jiān)控系統(tǒng)數(shù)據(jù)和行為的應(yīng)用才需要Service
豫领。
舉個例子,系統(tǒng)的OTA需要一個Service
在IVI的后臺監(jiān)控云服務(wù)或SOA接口的消息舔琅,然后完成升級包的下載等等恐。也需要一個HMI
顯示升級的Release Note、確認(rèn)用戶是否同意升級等搏明,這個HMI往往會被歸納在系統(tǒng)設(shè)置中鼠锈。Service
與HMI
之間的IPC通信闪檬,則需要暴露一個SDK
來完成星著,這個其他模塊的HMI
也可以通過這個SDK
完成與Service
的IPC通信。
反例則是粗悯,Launcher 可以長久的駐留在內(nèi)存虚循,所以它也就不需要Service
和SDK
。
本篇文章主要講解,如在HMI層中構(gòu)建一個適合車載系統(tǒng)應(yīng)用的MVVM架構(gòu)横缔。本文涉及的源碼:https://github.com/linux-link/CarMvvmArch
MVVM 架構(gòu)分層邏輯
MVVM 架構(gòu)的原理以及與MVC&MVP的區(qū)別铺遂,網(wǎng)上已經(jīng)有很多相關(guān)的優(yōu)秀文章,這里就不再贅述茎刚,本篇文章將聚焦如何車載應(yīng)用中利用Jetpack組件將 MVVM 架構(gòu)真正落地實(shí)現(xiàn)襟锐。
當(dāng)前的Android應(yīng)用的MVVM架構(gòu)分層邏輯,都源自圖-2 Android官方給出的指導(dǎo)建議膛锭,我們也同樣基于這套邏輯來實(shí)現(xiàn)MVVM架構(gòu)粮坞。
封裝適合車載應(yīng)用 MVVM 框架
車載應(yīng)用相對于手機(jī)應(yīng)用來說開發(fā)周期和復(fù)雜度都要小很多,所以我們封裝的重點(diǎn)是View層初狰,ViewModel 層和 Model 層的封裝則會相對簡單一些莫杈。
封裝 Model 層
一般來說我們會把訪問網(wǎng)絡(luò)的工具類封裝在Model層,但是車載系統(tǒng)應(yīng)用的 HMI 層通常沒有訪問網(wǎng)絡(luò)的功能奢入,所以 Model 層我們直接留空即可筝闹。
public abstract class BaseRepository {
}
封裝 ViewModel 層
VideModel 層的封裝很簡單,只需要將Model的實(shí)例傳入腥光,方便 ViewModel 的實(shí)現(xiàn)類調(diào)用即可关顷。
封裝 ViewModel
public abstract class BaseViewModel<M extends BaseRepository> extends ViewModel {
protected M mRepository;
public BaseViewModel(M repository) {
mRepository = repository;
}
public M getRepository() {
return mRepository;
}
}
封裝 AndroidViewModel
public abstract class BaseAndroidViewModel<M extends BaseRepository> extends AndroidViewModel {
protected M mRepository;
public BaseAndroidViewModel(Application application, @Nullable M repository) {
super(application);
mRepository = repository;
}
public M getRepository() {
return mRepository;
}
}
封裝 View 層
在 View 層中我們需要引入Databinding
和ViewModel
,并且定義出 View 的一些實(shí)現(xiàn)規(guī)范武福。
在實(shí)際使用中解寝,并不是每一個界面都需要使用MVVM架構(gòu), 所以需要額外封裝一個只引入Databinding
的 Frangment 和 Activity
基于 DataBinding 封裝 Fragment
public abstract class BaseBindingFragment<V extends ViewDataBinding> extends BaseFragment {
private static final String TAG = TAG_FWK + BaseBindingFragment.class.getSimpleName();
protected V mBinding;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
LogUtils.logV(TAG, "[onCreateView]");
if (getLayoutId() == 0) {
throw new RuntimeException("getLayout() must be not null");
}
mBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false);
mBinding.setLifecycleOwner(this);
mBinding.executePendingBindings();
initView();
return mBinding.getRoot();
}
protected abstract void initView();
@LayoutRes
protected abstract int getLayoutId();
public V getBinding() {
return mBinding;
}
}
在 BindingFragment 的基礎(chǔ)上添加 ViewModel
public abstract class BaseMvvmFragment<Vm extends BaseViewModel, V extends ViewDataBinding> extends BaseBindingFragment<V> {
protected Vm mViewModel;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
initViewModel();
View view = super.onCreateView(inflater, container, savedInstanceState);
initObservable(mViewModel);
if (getViewModelVariable() != 0) {
mBinding.setVariable(getViewModelVariable(), mViewModel);
}
return view;
}
@Override
public void onStart() {
super.onStart();
loadData(getViewModel());
}
private void initViewModel() {
Class<Vm> modelClass;
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
modelClass = (Class<Vm>) ((ParameterizedType) type).getActualTypeArguments()[0];
} else {
modelClass = (Class<Vm>) BaseViewModel.class;
}
Object object = getViewModelOrFactory();
if (object instanceof ViewModel){
mViewModel = (Vm) object;
}else if (object instanceof ViewModelProvider.Factory){
mViewModel = new ViewModelProvider(this, (ViewModelProvider.Factory) object)
.get(modelClass);
}else {
mViewModel = new ViewModelProvider(this,
new ViewModelProvider.NewInstanceFactory()).get(modelClass);
}
}
protected abstract Object getViewModelOrFactory();
protected abstract int getViewModelVariable();
protected abstract void initObservable(Vm viewModel);
protected abstract void loadData(Vm viewModel);
protected Vm getViewModel() {
return mViewModel;
}
}
基于 DataBinding 封裝 Activity
public abstract class BaseBindingActivity<V extends ViewDataBinding> extends BaseActivity {
protected V mBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getLayoutId() == 0) {
throw new RuntimeException("getLayout() must be not null");
}
mBinding = DataBindingUtil.setContentView(this, getLayoutId());
mBinding.setLifecycleOwner(this);
mBinding.executePendingBindings();
initView();
}
@LayoutRes
protected abstract int getLayoutId();
public V getBinding() {
return mBinding;
}
protected abstract void initView();
}
在 BindingActivity 的基礎(chǔ)上添加 ViewModel
public abstract class BaseMvvmActivity<Vm extends BaseViewModel, V extends ViewDataBinding> extends BaseBindingActivity<V> {
protected Vm mViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
initViewModel();
super.onCreate(savedInstanceState);
if (getViewModelVariable() != 0) {
mBinding.setVariable(getViewModelVariable(), mViewModel);
}
mBinding.executePendingBindings();
initObservable(mViewModel);
}
@Override
protected void onStart() {
super.onStart();
loadData(mViewModel);
}
private void initViewModel() {
Class<Vm> modelClass;
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
modelClass = (Class<Vm>) ((ParameterizedType) type).getActualTypeArguments()[0];
} else {
modelClass = (Class<Vm>) BaseViewModel.class;
}
Object object = getViewModelOrFactory();
if (object instanceof BaseViewModel){
mViewModel = (Vm) object;
}else if (object instanceof ViewModelProvider.Factory){
mViewModel = new ViewModelProvider(this, (ViewModelProvider.Factory) object)
.get(modelClass);
}else {
mViewModel = new ViewModelProvider(this,
new ViewModelProvider.NewInstanceFactory()).get(modelClass);
}
}
protected abstract Object getViewModelOrFactory();
protected abstract int getViewModelVariable();
protected abstract void initObservable(Vm viewModel);
protected abstract void loadData(Vm viewModel);
protected Vm getViewModel() {
return mViewModel;
}
}
重點(diǎn)解釋一下幾個abstract的方法
- Object getViewModelOrFactory()
返回ViewModel的實(shí)例或ViewModelFactory實(shí)例
- int getViewModelVariable()
返回XML中ViewModel的Variable****Id艘儒。例如:BR.viewModel.
- void initObservable(Vm viewModel)
在此處操作ViewModel中LiveData的聋伦。例如:下面這類方法,都應(yīng)該寫在這個方法體里面界睁。目的是為了便于維護(hù)
viewModel.getTempLive().observe(this, new Observer<String>() {
@Override
public void onChanged(String temp) {
LogUtils.logI(TAG, "[onChanged] " + temp);
}
});
- void initView()
在此處進(jìn)行初始化UI的操作觉增。例如:初始化RecyclerView,設(shè)定ClickListener等等翻斟。
- void loadData(Vm viewModel)
在此處使用ViewModel
進(jìn)行請求用于初始化UI的數(shù)據(jù)逾礁。
基于框架實(shí)現(xiàn)MVVM架構(gòu)
接下來我們基于上面封裝的 MVVM 框架,來實(shí)現(xiàn)一個最基礎(chǔ)的 MVVM 架構(gòu)下的demo访惜。
定義公共組件
創(chuàng)建 ViewModelFactory
定義ViewModel
的實(shí)例化方式嘹履,單一Module下ViewModel
的創(chuàng)建應(yīng)該集中在一個ViewModelFactory
中
// default 權(quán)限,不對外部公開此類
class AppViewModelFactory implements ViewModelProvider.Factory {
// 創(chuàng)建 viewModel 實(shí)例
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
try {
if (modelClass == HvacViewModel.class) {
return modelClass.getConstructor(HvacRepository.class, AppExecutors.class)
.newInstance(AppInjection.getHvacRepository(), AppExecutors.get());
} else {
throw new RuntimeException(modelClass.getSimpleName() + "create failed");
}
} catch (NoSuchMethodException | IllegalAccessException
| InstantiationException | InvocationTargetException exception) {
exception.printStackTrace();
throw new RuntimeException(exception);
}
}
}
創(chuàng)建 AppInjection
如果應(yīng)用中沒有使用 Dagger 或 Hilt 等依賴注入框架债热,那么為了便于日后的維護(hù)砾嫉,無論是車載應(yīng)用還是手機(jī)應(yīng)用,都建議定義一個AppInjection
來將應(yīng)用中的單例窒篱、ViewModel焕刮、Repository等實(shí)例的獲取統(tǒng)一到一個入口程序中舶沿。
public class AppInjection {
// ViewModel 工廠
private final static AppViewModelFactory mViewModelFactory = new AppViewModelFactory();
public static <T extends ViewModel> T getViewModel(ViewModelStoreOwner store, Class<T> clazz) {
return new ViewModelProvider(store, mViewModelFactory).get(clazz);
}
public static AppViewModelFactory getViewModelFactory() {
return mViewModelFactory;
}
/**
* 受保護(hù)的權(quán)限,除了ViewModel,其它模塊不應(yīng)該需要Model層的實(shí)例
*
* @return {@link HvacRepository}
*/
protected static HvacRepository getHvacRepository() {
return new HvacRepository(getHvacManager());
}
public static HvacManager getHvacManager() {
return HvacManager.getInstance();
}
}
構(gòu)建 Model 層
在車載應(yīng)用中 Model 層的主要數(shù)據(jù)源無外乎 有三種網(wǎng)絡(luò)數(shù)據(jù)源配并、HMI本地數(shù)據(jù)源括荡、IPC(進(jìn)程間通信)數(shù)據(jù)源,其中最常見的是只有IPC數(shù)據(jù)源溉旋,三種數(shù)據(jù)源都有的情況往往會出現(xiàn)在主機(jī)廠商自行開發(fā)的車載地圖應(yīng)用中畸冲。所以我們這里只考慮如何基于IPC數(shù)據(jù)源構(gòu)造Model層
定義一個 XXX``Repository
繼承自 BaseRepository
,再根據(jù)業(yè)務(wù)需要定義出我們需要使用的接口观腊,這里的HvacManager
就是service提供的用來進(jìn)行跨進(jìn)程通信的IPC-SDK中的入口召夹。
public class HvacRepository extends BaseRepository {
private static final String TAG = IpcApp.TAG_HVAC + HvacRepository.class.getSimpleName();
private final HvacManager mHvacManager;
private HvacCallback mHvacViewModelCallback;
private final IHvacCallback mHvacCallback = new IHvacCallback() {
@Override
public void onTemperatureChanged(double temp) {
if (mHvacViewModelCallback != null) {
// 處理遠(yuǎn)程數(shù)據(jù),講他轉(zhuǎn)換為應(yīng)用中需要的數(shù)據(jù)格式或內(nèi)容
String value = String.valueOf(temp);
mHvacViewModelCallback.onTemperatureChanged(value);
}
}
};
public HvacRepository(HvacManager hvacManager) {
mHvacManager = hvacManager;
mHvacManager.registerCallback(mHvacCallback);
}
public void clear() {
mHvacManager.unregisterCallback(mHvacCallback);
}
public void requestTemperature() {
LogUtils.logI(TAG, "[requestTemperature]");
mHvacManager.requestTemperature();
}
public void setTemperature(int temperature) {
LogUtils.logI(TAG, "[setTemperature] " + temperature);
mHvacManager.setTemperature(temperature);
}
public void setHvacListener(HvacCallback callback) {
LogUtils.logI(TAG, "[setHvacListener] " + callback);
mHvacViewModelCallback = callback;
}
public void removeHvacListener(HvacCallback callback) {
LogUtils.logI(TAG, "[removeHvacListener] " + callback);
mHvacViewModelCallback = null;
}
}
Repository
通過一個HvacCallback
將監(jiān)聽的遠(yuǎn)程數(shù)據(jù)處理后返回給ViewModel
恕沫。
如果應(yīng)用會與多個不同的模塊進(jìn)行IPC通信监憎,那么建議將這些由不同模塊提供的IPC-SDK封裝在一個Manager中進(jìn)行統(tǒng)一管理。
構(gòu)建ViewModel
在Jetpack中ViewModel
的用途是封裝界面控制器的數(shù)據(jù)婶溯,以使數(shù)據(jù)在配置更改后仍然存在鲸阔。在Android的MVVM 架構(gòu)設(shè)計中,ViewModel
是最關(guān)鍵的一層迄委,通過持有Repository
的引用來進(jìn)行外部通信
public class HvacViewModel extends BaseViewModel<HvacRepository> {
private static final String TAG = IpcApp.TAG_HVAC + HvacViewModel.class.getSimpleName();
private final HvacRepository mRepository;
// 線程池框架褐筛。某些場景,ViewModel訪問Repository中的方法可能會需要切換到子線程叙身。
private final AppExecutors mAppExecutors;
private MutableLiveData<String> mTempLive;
private final HvacCallback mHvacCallback = new HvacCallback() {
@Override
public void onTemperatureChanged(String temp) {
LogUtils.logI(TAG, "[onTemperatureChanged] " + temp);
getTempLive().postValue(temp);
}
};
public HvacViewModel(HvacRepository repository, AppExecutors executors) {
super(repository);
mRepository = repository;
mAppExecutors = executors;
mRepository.setHvacListener(mHvacCallback);
}
@Override
protected void onCleared() {
super.onCleared();
mRepository.removeHvacListener(mHvacCallback);
mRepository.release();
}
/**
* 請求頁面數(shù)據(jù)
*/
public void requestTemperature() {
mRepository.requestTemperature();
}
/**
* 將溫度數(shù)據(jù)設(shè)定到Service中
*
* @param view
*/
public void setTemperature(View view) {
mRepository.setTemperature(getTempLive().getValue());
}
public MutableLiveData<String> getTempLive() {
if (mTempLive == null) {
mTempLive = new MutableLiveData<>();
}
return mTempLive;
}
}
構(gòu)建View層
最后就是構(gòu)建View層渔扎,一把就是Activity/Fragment和XML。
HvacActivity
中各個方法含義我們上面封裝BaseMvvmActivity的時候已經(jīng)解釋過了信轿,這里不再贅述晃痴。
public class HvacActivity extends BaseMvvmActivity<HvacViewModel, ActivityHvacBinding> {
private static final String TAG = IpcApp.TAG_HVAC + HvacActivity.class.getSimpleName();
@Override
protected int getLayoutId() {
return R.layout.activity_hvac;
}
@Override
protected Object getViewModelOrFactory() {
return AppInjection.getViewModelFactory();
}
@Override
protected int getViewModelVariable() {
return BR.viewModel;
}
@Override
protected void initView() {
}
@Override
protected void initObservable(HvacViewModel viewModel) {
viewModel.getTempLive().observe(this, new Observer<String>() {
@Override
public void onChanged(String temp) {
LogUtils.logI(TAG, "[onChanged] " + temp);
}
});
}
@Override
protected void loadData(HvacViewModel viewModel) {
viewModel.requestTemperature();
}
}
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.mvvm.hmi.ipc.ui.HvacViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="36dp"
android:onClick="@{viewModel::setTemperature}"
android:text="確定"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_temperature" />
<EditText
android:id="@+id/et_temperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@={viewModel.tempLive}"
app:layout_constraintBottom_toBottomOf="@+id/textView"
app:layout_constraintStart_toEndOf="@+id/textView"
app:layout_constraintTop_toTopOf="@+id/textView" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:text="Temperature:"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
以上就是如何封裝一個適合車載應(yīng)用使用的 MVVM 框架。不知道你有沒有發(fā)現(xiàn)财忽,在HMI中使用AIDL方法倘核。通常是比較麻煩的。我們需要在HMI與Service完成綁定后即彪,我們才能調(diào)用Service中實(shí)現(xiàn)的Binder方法紧唱。但是示例中我們使用的SDK,并沒進(jìn)行綁定操作隶校,而是直接進(jìn)行調(diào)用漏益。關(guān)于如何編寫基于AIDL的SDK,就放到下一章再介紹深胳,感謝您的閱讀绰疤。
本文所涉及的源碼請訪問:https://github.com/linux-link/CarMvvmArch