Android 车载应用开发与分析 (3)- 构建 MVVM 架构(Java版)

简介:

前言

在大多数车载系统应用架构中,一个完整的应用往往会包含三层:

  • HMI
    Human Machine Interface,显示UI信息,进行人机交互。

  • Service
    在系统后台进行数据处理,监控数据状态。

  • SDK
    根据业务逻辑Service对外暴露的通信接口,其他模块通过它来完成IPC通信。

当然并不是所有的应用都需要Service,只有不能长久的驻留在内存中,且需要监控系统数据和行为的应用才需要Service

举个例子,系统的OTA需要一个Service在IVI的后台监控云服务或SOA接口的消息,然后完成升级包的下载等。也需要一个HMI显示升级的Release Note、确认用户是否同意升级等,这个HMI往往会被归纳在系统设置中。ServiceHMI之间的IPC通信,则需要暴露一个SDK来完成,这个其他模块的HMI也可以通过这个SDK完成与Service的IPC通信。

反例则是,Launcher 可以长久的驻留在内存,所以它也就不需要ServiceSDK

本篇文章主要讲解,如在HMI层中构建一个适合车载系统应用的MVVM架构。本文涉及的源码:https://github.com/linux-link/CarMvvmArch

MVVM 架构分层逻辑

MVVM 架构的原理以及与MVC&MVP的区别,网上已经有很多相关的优秀文章,这里就不再赘述,本篇文章将聚焦如何车载应用中利用Jetpack组件将 MVVM 架构真正落地实现。

image.png

当前的Android应用的MVVM架构分层逻辑,都源自图-2 Android官方给出的指导建议,我们也同样基于这套逻辑来实现MVVM架构。

image.png

封装适合车载应用 MVVM 框架

车载应用相对于手机应用来说开发周期和复杂度都要小很多,所以我们封装的重点是View层,ViewModel 层和 Model 层的封装则会相对简单一些。

封装 Model 层

一般来说我们会把访问网络的工具类封装在Model层,但是车载系统应用的 HMI 层通常没有访问网络的功能,所以 Model 层我们直接留空即可。

public abstract class BaseRepository {

}

封装 ViewModel 层

VideModel 层的封装很简单,只需要将Model的实例传入,方便 ViewModel 的实现类调用即可。

封装 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 层中我们需要引入DatabindingViewModel,并且定义出 View 的一些实现规范。

在实际使用中,并不是每一个界面都需要使用MVVM架构, 所以需要额外封装一个只引入DatabindingFrangmentActivity

基于 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 的基础上添加 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 的基础上添加 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;
    }
}

重点解释一下几个abstract的方法

  • Object getViewModelOrFactory()

返回ViewModel的实例或ViewModelFactory实例

  • int getViewModelVariable()

返回XML中ViewModel的Variable**Id。例如:BR.viewModel.**

  • void initObservable(Vm viewModel)

在此处操作ViewModel中LiveData的。例如:下面这类方法,都应该写在这个方法体里面。目的是为了便于维护

viewModel.getTempLive().observe(this, new Observer<String>() {
    @Override
    public void onChanged(String temp) {
        LogUtils.logI(TAG, "[onChanged] " + temp);
    }
});
  • void initView()

在此处进行初始化UI的操作。例如:初始化RecyclerView,设定ClickListener等等。

  • void loadData(Vm viewModel)

在此处使用ViewModel进行请求用于初始化UI的数据。

基于框架实现MVVM架构

接下来我们基于上面封装的 MVVM 框架,来实现一个最基础的 MVVM 架构下的demo。

定义公共组件

创建 ViewModelFactory

定义ViewModel的实例化方式,单一Module下ViewModel的创建应该集中在一个ViewModelFactory

// default 权限,不对外部公开此类
class AppViewModelFactory implements ViewModelProvider.Factory {

    // 创建 viewModel 实例
    @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);
        }
    }
}

创建 AppInjection

如果应用中没有使用 DaggerHilt 等依赖注入框架,那么为了便于日后的维护,无论是车载应用还是手机应用,都建议定义一个AppInjection来将应用中的单例、ViewModel、Repository等实例的获取统一到一个入口程序中。

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;
    }

    /**
     * 受保护的权限,除了ViewModel,其它模块不应该需要Model层的实例
     *
     * @return {@link HvacRepository}
     */
    protected static HvacRepository getHvacRepository() {
        return new HvacRepository(getHvacManager());
    }

    public static HvacManager getHvacManager() {
        return HvacManager.getInstance();
    }

}

构建 Model 层

在车载应用中 Model 层的主要数据源无外乎 有三种网络数据源HMI本地数据源IPC(进程间通信)数据源,其中最常见的是只有IPC数据源,三种数据源都有的情况往往会出现在主机厂商自行开发的车载地图应用中。所以我们这里只考虑如何基于IPC数据源构造Model

定义一个 XXX``Repository 继承自 BaseRepository,再根据业务需要定义出我们需要使用的接口,这里的HvacManager就是service提供的用来进行跨进程通信的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) {
                // 处理远程数据,讲他转换为应用中需要的数据格式或内容
                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将监听的远程数据处理后返回给ViewModel

如果应用会与多个不同的模块进行IPC通信,那么建议将这些由不同模块提供的IPC-SDK封装在一个Manager中进行统一管理。

构建ViewModel

在Jetpack中ViewModel的用途是封装界面控制器的数据,以使数据在配置更改后仍然存在。在Android的MVVM 架构设计中,ViewModel是最关键的一层,通过持有Repository的引用来进行外部通信

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();
    }

    /**
     * 请求页面数据
     */
    public void requestTemperature() {
        mRepository.requestTemperature();
    }

    /**
     * 将温度数据设定到Service中
     *
     * @param view
     */
    public void setTemperature(View view) {
        mRepository.setTemperature(getTempLive().getValue());
    }

    public MutableLiveData<String> getTempLive() {
        if (mTempLive == null) {
            mTempLive = new MutableLiveData<>();
        }
        return mTempLive;
    }
}

构建View层

最后就是构建View层,一把就是Activity/Fragment和XML。

HvacActivity中各个方法含义我们上面封装BaseMvvmActivity的时候已经解释过了,这里不再赘述。

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>

以上就是如何封装一个适合车载应用使用的 MVVM 框架。不知道你有没有发现,在HMI中使用AIDL方法。通常是比较麻烦的。我们需要在HMI与Service完成绑定后,我们才能调用Service中实现的Binder方法。但是示例中我们使用的SDK,并没进行绑定操作,而是直接进行调用。关于如何编写基于AIDL的SDK,就放到下一章再介绍,感谢您的阅读。

本文所涉及的源码请访问:https://github.com/linux-link/CarMvvmArch

参考资料

应用架构指南 | Android 开发者 | Android Developers

目录
相关文章
|
1月前
|
人工智能 算法 Java
Java与AI驱动区块链:构建智能合约与去中心化AI应用
区块链技术和人工智能的融合正在开创去中心化智能应用的新纪元。本文深入探讨如何使用Java构建AI驱动的区块链应用,涵盖智能合约开发、去中心化AI模型训练与推理、数据隐私保护以及通证经济激励等核心主题。我们将完整展示从区块链基础集成、智能合约编写、AI模型上链到去中心化应用(DApp)开发的全流程,为构建下一代可信、透明的智能去中心化系统提供完整技术方案。
222 3
|
2月前
|
人工智能 缓存 监控
使用LangChain4j构建Java AI智能体:让大模型学会使用工具
AI智能体是大模型技术的重要演进方向,它使模型能够主动使用工具、与环境交互,以完成复杂任务。本文详细介绍如何在Java应用中,借助LangChain4j框架构建一个具备工具使用能力的AI智能体。我们将创建一个能够进行数学计算和实时信息查询的智能体,涵盖工具定义、智能体组装、记忆管理以及Spring Boot集成等关键步骤,并展示如何通过简单的对话界面与智能体交互。
865 1
|
1月前
|
人工智能 缓存 自然语言处理
Java与多模态AI:构建支持文本、图像和音频的智能应用
随着大模型从单一文本处理向多模态能力演进,现代AI应用需要同时处理文本、图像、音频等多种信息形式。本文深入探讨如何在Java生态中构建支持多模态AI能力的智能应用。我们将完整展示集成视觉模型、语音模型和语言模型的实践方案,涵盖从文件预处理、多模态推理到结果融合的全流程,为Java开发者打开通往下一代多模态AI应用的大门。
302 41
|
29天前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
215 8
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
Java与生成式AI:构建内容生成与创意辅助系统
生成式AI正在重塑内容创作、软件开发和创意设计的方式。本文深入探讨如何在Java生态中构建支持文本、图像、代码等多种生成任务的创意辅助系统。我们将完整展示集成大型生成模型(如GPT、Stable Diffusion)、处理生成任务队列、优化生成结果以及构建企业级生成式AI应用的全流程,为Java开发者提供构建下一代创意辅助系统的完整技术方案。
160 10
|
1月前
|
人工智能 Java 物联网
Java与边缘AI:构建离线智能的物联网与移动应用
随着边缘计算和终端设备算力的飞速发展,AI推理正从云端向边缘端迁移。本文深入探讨如何在资源受限的边缘设备上使用Java构建离线智能应用,涵盖从模型优化、推理加速到资源管理的全流程。我们将完整展示在Android设备、嵌入式系统和IoT网关中部署轻量级AI模型的技术方案,为构建真正实时、隐私安全的边缘智能应用提供完整实践指南。
250 3
|
1月前
|
人工智能 监控 Java
Java与AI智能体:构建自主决策与工具调用的智能系统
随着AI智能体技术的快速发展,构建能够自主理解任务、制定计划并执行复杂操作的智能系统已成为新的技术前沿。本文深入探讨如何在Java生态中构建具备工具调用、记忆管理和自主决策能力的AI智能体系统。我们将完整展示从智能体架构设计、工具生态系统、记忆机制到多智能体协作的全流程,为Java开发者提供构建下一代自主智能系统的完整技术方案。
360 4
|
2月前
|
人工智能 Java API
Java AI智能体实战:使用LangChain4j构建能使用工具的AI助手
随着AI技术的发展,AI智能体(Agent)能够通过使用工具来执行复杂任务,从而大幅扩展其能力边界。本文介绍如何在Java中使用LangChain4j框架构建一个能够使用外部工具的AI智能体。我们将通过一个具体示例——一个能获取天气信息和执行数学计算的AI助手,详细讲解如何定义工具、创建智能体并处理执行流程。本文包含完整的代码示例和架构说明,帮助Java开发者快速上手AI智能体的开发。
947 8
|
1月前
|
机器学习/深度学习 人工智能 监控
Java与AI模型部署:构建企业级模型服务与生命周期管理平台
随着企业AI模型数量的快速增长,模型部署与生命周期管理成为确保AI应用稳定运行的关键。本文深入探讨如何使用Java生态构建一个企业级的模型服务平台,实现模型的版本控制、A/B测试、灰度发布、监控与回滚。通过集成Spring Boot、Kubernetes、MLflow和监控工具,我们将展示如何构建一个高可用、可扩展的模型服务架构,为大规模AI应用提供坚实的运维基础。
222 0
|
1月前
|
机器学习/深度学习 分布式计算 Java
Java与图神经网络:构建企业级知识图谱与智能推理系统
图神经网络(GNN)作为处理非欧几里得数据的前沿技术,正成为企业知识管理和智能推理的核心引擎。本文深入探讨如何在Java生态中构建基于GNN的知识图谱系统,涵盖从图数据建模、GNN模型集成、分布式图计算到实时推理的全流程。通过具体的代码实现和架构设计,展示如何将先进的图神经网络技术融入传统Java企业应用,为构建下一代智能决策系统提供完整解决方案。
278 0

热门文章

最新文章