Android极简MVVM,从一个基类库谈起

简介: 这篇文章,主要详细介绍如何封装一个MVVM的基类库,以及MVVM架构模式在实际业务中的用法,最后会把实际的封装代码开源,并提供远程依赖。

Hello啊各位老铁,今天带来一个老生常谈的技术,MVVM,这篇文章,主要详细介绍如何封装一个MVVM的基类库,以及MVVM架构模式在实际业务中的用法,最后会把实际的封装代码开源,并提供远程依赖,方便给到大家使用以及二次修改,尽量做到细致入微,浅显易懂,OK,废话不多赘述,我们进入正文。


这篇文章大概会按照以下几个模块进行阐述,此次封装,做到绝无第三方依赖,都是Android原生的代码封装,请放心使用,如果您想直接进行使用,请直接跳到第4步,集成使用即可,此次的封装,和目前主流的MVVM架构模式,会完美契合,让架构模式简单化,让业务代码清晰化,必须值得推荐使用。


一、MVVM简单概括

二、基于MVVM模式如何封装基类库

三、实战封装

四、封装后在业务中如何使用

五、开源以及Demo查看


一、MVVM简单概括


MVVM的开发模式,相对来说低耦合,业务之间逻辑显得也十分分明,Model层负责将请求的数据交给ViewModel层;ViewModel层负责将请求到的数据做业务逻辑处理,最后交给View层去展示,与View一一对应;View层只负责界面绘制刷新,不处理业务逻辑,非常适合进行独立模块开发。

三层简单概括

1、Model:数据层,包含数据实体和对数据实体的操作。
2、View:视图层,对应于Activity,XML,View,负责数据显示以及用户交互。
3、ViewModel:关联层,将Model和View进行绑定,Model或者View更改时,实时刷新对方。


需要注意:


1、View只做和UI相关的工作,不涉及任何业务逻辑,不涉及操作数据,不处理数据,也就是UI和数据是严格分开的。

2、ViewModel只做和业务逻辑相关的工作,不涉及任何和UI相关的操作,不持有控件引用,不更新UI。


二、基于MVVM模式如何封装基类库


MVVM我们已经清晰,然而针对现有的三层,我们如何进行拆解封装呢?面对这样的一个问题,我们也是需要从三层以及和实际的业务进行相结合,从实际业务中来,也要从实际业务中去,这是我们封装的一个潜在因素,一旦脱离了实际,封装的再优秀,也只是一个花瓶,中看不中用。


针对MVVM中的三层,其实,我们在封装中,也是基于这三层,View,ViewModel和Model。View中,在实际的开发中,一般针对Activity和Fragment进行系统的抽取封装,ViewModel一般会抽取一个父类,做一些公共的方法或属性配置,Model层一般封装的较少,根据实际业务,需要具体问题具体分析。


Activity和Fragment的封装思路,其实是一致的,需要以简单和复杂两种方向进行抽取,一种是简单的页面继承使用,一种是复杂的页面继承使用,这样区分的一个目的,就是,专职专用,避免大材小用,而具体的封装,除了使得代码简洁化,更重要的拓展化,方便子类的调用。


在具体封装的时候,与实际业务相结合,这个无比重要,比如实际的大部分页面,都带有一个标题栏,那么标题栏就可以直接封装父类里面,像子类拓展出,更改标题,右侧按钮,左侧按钮等功能属性;除了统一的标题栏,另外就是子类的视图了,关于子类的视图传递,这个是必须的,可以直接抽象出一个必须要实现的方法,其他的,比如状态栏的改变,缺省页的设置等等,也需要在父类中统一的给出。


复杂的页面是基于简单的页面而来的,这里的复杂,一般是包含很多逻辑的处理,那么,我们就可以增加ViewModel层和Model层了,目前基于DataBinding的实现方式,无论简单和复杂,都是必须需要考虑的,也就是说在父类中,我们就需要向子类提供出可以拿到的databinding和viewmodel,这样子类再继承的时候,就可以很方便的进行调用。


在复杂的页面,也就是包含ViewModel层和Model层的时候,需要考虑绑定视图variable的传递,也就是当前的ViewModel和那个xml进行绑定,当然这是在需要的时候,必须要操作的,除了视图绑定,常见的,数据请求状态,比如请求成功,请求失败,缺省页显示和隐藏,Dialog的显示和隐藏,LiveData的数据回传等等,在复杂的页面中也是需要我们考虑的。


除了以上的常规考虑,在实际的业务中,比如事件消息传递,PagerAdapter使用,状态栏透明等很多和基类的相关的功能,我们其实也可以进行封装进去,便于子类的调用。


三、实战封装


通过第2条中的拆解和具体的封装思路,不妨我们进行实战一下,由于Activity和Fragment的封装思路以及相关属性和方法,大部分都是雷同的,所以目前只介绍Activity,更详细的封装,还请大家参考源码。


1、Activity的简单封装


简单封装,不携带ViewModel,只传递ViewDataBinding,子类必须重写的方法只有一个initData,其他均为选择性重写,如果相对逻辑比较简单的页面,可以继承此类。


一个很简单的普通封装,就是把共有的常见的,封装到父类里,便于子类的调用,具体什么方法,什么逻辑进行采取封装,需要我们根据具体业务或者公司的相关情况而定,以下是源码。


abstractclassBaseActivity<VB : ViewDataBinding>(@LayoutReslayoutId: Int=0) :
AppCompatActivity(layoutId) {
privatevarmActionBarView: ActionBarView?=nullprivatevarmLayoutError: LinearLayout?=nullprivatevarmLayoutId=layoutIdlateinitvarmBinding: VBoverridefunonCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
//默认状态栏为白底黑字darkMode(BaseConfig.statusBarDarkMode)
statusBarColor(ContextCompat.getColor(this, BaseConfig.statusBarColor))
setContentView(R.layout.activity_base)
valbaseChild=findViewById<LinearLayout>(R.id.layout_base_child)
mLayoutError=findViewById(R.id.layout_empty_or_error)
mActionBarView=findViewById(R.id.action_bar)
if (mLayoutId==0) {
mLayoutId=getLayoutId()
            }
if (savedInstanceState!=null&&getIntercept()) {
noEmptyBundle()
return            }
valchildView=layoutInflater.inflate(mLayoutId, null)
baseChild.addView(childView)
mBinding=DataBindingUtil.bind(childView)!!initView()
initData()
        } catch (e: Exception) {
e.printStackTrace()
noEmptyBundle()
        }
    }
/*** AUTHOR:AbnerMing* INTRODUCE:获取视图id*/openfungetLayoutId(): Int {
return0    }
openfuninitView() {}
/*** AUTHOR:AbnerMing* INTRODUCE:初始化数据*/abstractfuninitData()
/*** AUTHOR:AbnerMing* INTRODUCE:动态改变状态栏颜色和标题*/funsetDarkTitle(dark: Boolean, color: Int, title: String) {
try {
darkMode(dark)
statusBarColor(ContextCompat.getColor(this, color))
setBarTitle(title)
        } catch (e: Exception) {
e.printStackTrace()
        }
    }
/*** AUTHOR:AbnerMing* INTRODUCE:设置标题*/funsetBarTitle(title: String) {
mActionBarView!!.visibility=View.VISIBLEmActionBarView!!.setBarTitle(title)
    }
/*** AUTHOR:AbnerMing* INTRODUCE:隐藏左侧按钮*/funhintLeftMenu() {
mActionBarView!!.hintLeftBack()
    }
/*** AUTHOR:AbnerMing* INTRODUCE:获取ActionBarView*/fungetActionBarView(): ActionBarView {
returnmActionBarView!!    }
/*** AUTHOR:AbnerMing* INTRODUCE:隐藏标题栏*/funhintActionBar() {
mActionBarView?.visibility=View.GONE    }
/*** AUTHOR:AbnerMing* INTRODUCE:Bundle为空进行拦截,解决改变权限后重回App崩溃问题*/openfungetIntercept(): Boolean {
returnfalse    }
/*** AUTHOR:AbnerMing* INTRODUCE:Bundle为空时的逻辑处理,解决改变权限后重回App崩溃问题*/openfunnoEmptyBundle() {}
overridefunonDestroy() {
super.onDestroy()
try {
LiveDataBus.removeObserve(this)
LiveDataBus.removeStickyObserver(this)
        } catch (e: Exception) {
e.printStackTrace()
        }
    }
/*** AUTHOR:AbnerMing* INTRODUCE:透明状态栏*/funtranslucentWindow(dark: Boolean) {
try {
immersive(0, dark)
        } catch (e: Exception) {
e.printStackTrace()
        }
    }
/*** AUTHOR:AbnerMing* INTRODUCE:设置缺省页*/funsetEmptyOrError(view: View) {
mLayoutError?.visibility=View.VISIBLEmLayoutError?.removeAllViews()
mLayoutError?.addView(view)
    }
/*** AUTHOR:AbnerMing* INTRODUCE:隐藏*/funhintEmptyOrErrorView() {
mLayoutError?.visibility=View.GONE    }
/*** AUTHOR:AbnerMing* INTRODUCE:获取错误或为空的view*/fungetEmptyOrErrorView(): LinearLayout {
returnmLayoutError!!    }
}


涉及的方法概述


方法名

参数

概述

getLayoutId

无参

子类传递的layout,用于加载视图,可以通过构造方法传递,也可以通过此方法传递。

initView

无参

初始化View,非必须重写

initData

无参

初始化数据

setDarkTitle

dark: Boolean, color: Int, title: String

1、dark: Boolean,状态栏颜色,true就是黑色,false就是白色。

2、color: Int,状态栏背景颜色

3、title: String,标题栏内容

设置标题,状态栏背景及颜色

setBarTitle

title: String,标题栏内容

设置标题

hintLeftMenu

无参

隐藏左侧按钮

getActionBarView

无参

获取标题栏View,可以操作标题栏里的任何控件

hintActionBar

无参

隐藏标题栏

translucentWindow

dark: Boolean,状态栏颜色,true就是黑色,false就是白色

透明状态栏

setEmptyOrError

view: View,传递的缺省View视图

设置缺省视图

hintEmptyOrErrorView

无参

隐藏缺省视图

getEmptyOrErrorView

无参

获取缺省视图


简单的Activity没有什么好说的,都是中规中矩,具体的使用请大家看第四条,具体使用即可。


2、Activity的复杂封装


也谈不上复杂,只是在继承简单页面的基础之上多加了一个ViewModel,相对于比较复杂的页面,就可以继承此类,此类,拓展了ViewModel,可以在ViewModel里进行逻辑的书写,此类也是MVVM的标准执行,V继承于BaseVMActivity,VM继承于BaseViewModel,至于M,可以在VM中通过getRepository方法进行获取。


具体代码逻辑如下:


BaseVMActivity继承于BaseActivity。


abstractclassBaseVMActivity<VB : ViewDataBinding, BM : BaseViewModel>(@LayoutReslayoutId: Int=0) :
BaseActivity<VB>(layoutId) {
lateinitvarmViewModel: BMoverridefuninitData() {
mViewModel=getViewModel()!!valvariableId=getVariableId()
if (variableId!=-1) {
mBinding.setVariable(getVariableId(), mViewModel)
mBinding.executePendingBindings()
        }
initVMData()
observeLiveData()
initState()
    }
/*** AUTHOR:AbnerMing* INTRODUCE:获取绑定的xml id*/openfungetVariableId(): Int {
return-1    }
/*** AUTHOR:AbnerMing* INTRODUCE:初始化状态*/privatefuninitState() {
mViewModel.mStateViewLiveData.observe(this, {
when (it) {
StateLayoutEnum.DIALOG_LOADING-> {
dialogLoading()
                }
StateLayoutEnum.DIALOGD_DISMISS-> {
dialogDismiss()
                }
StateLayoutEnum.DATA_ERROR-> {
dataError()
                }
StateLayoutEnum.DATA_NULL-> {
dataEmpty()
                }
StateLayoutEnum.NET_ERROR-> {
netError()
                }
StateLayoutEnum.HIDE-> {
hide()
                }
            }
        })
    }
/*** AUTHOR:AbnerMing* INTRODUCE:初始化数据*/abstractfuninitVMData()
/*** AUTHOR:AbnerMing* INTRODUCE:LiveData的Observer*/openfunobserveLiveData() {
    }
/*** AUTHOR:AbnerMing* INTRODUCE:dialog加载*/openfundialogLoading() {}
/*** AUTHOR:AbnerMing* INTRODUCE:dialog隐藏*/openfundialogDismiss() {}
/*** AUTHOR:AbnerMing* INTRODUCE:数据错误*/openfundataError() {}
/*** AUTHOR:AbnerMing* INTRODUCE:数据为空*/openfundataEmpty() {}
/*** AUTHOR:AbnerMing* INTRODUCE:网络错误或请求错误*/openfunnetError() {}
/*** AUTHOR:AbnerMing* INTRODUCE:隐藏某些布局或者缺省页等*/openfunhide() {}
privatefungetViewModel(): BM? {
//这里获得到的是类的泛型的类型valtype=javaClass.genericSuperclassif (type!=null&&typeisParameterizedType) {
valactualTypeArguments=type.actualTypeArgumentsvaltClass=actualTypeArguments[1]
returnViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory.getInstance(application)
            )
                .get(tClassasClass<BM>)
        }
returnnull    }
}


封装涉及的方法概述


方法名

参数

概述

getVariableId

无参

获取绑定的xml variable,也就是当前的xml和哪个对象进行绑定,用于xml里直接数据绑定

initVMData

无参

初始化数据,必须要实现的方法

observeLiveData

无参

LiveData的Observer,UI层监听ViewModel层的数据改变

dialogLoading

无参

dialog加载

dialogDismiss

无参

dialog隐藏

dataError

无参

数据错误

dataEmpty

无参

数据为空

netError

无参

数据错误

hide

无参

隐藏缺省页等其他页面


BaseViewModel


BaseViewModel相对比较简单,只提供了一个可以获取Repository的方法,还有一个是刷新UI视图的一个LiveData,就是数据请求,Dialog加载,缺省页加载的状态。


openclassBaseViewModel : ViewModel() {
/*** 控制状态视图的LiveData*/valmStateViewLiveData=MutableLiveData<StateLayoutEnum>()
/*** 更改状态视图的状态*/publicfunchangeStateView(
state: StateLayoutEnum    ) {
// 对参数进行校验when (state) {
StateLayoutEnum.DIALOG_LOADING-> {
mStateViewLiveData.postValue(StateLayoutEnum.DIALOG_LOADING)
            }
StateLayoutEnum.DIALOGD_DISMISS-> {
mStateViewLiveData.postValue(StateLayoutEnum.DIALOGD_DISMISS)
            }
StateLayoutEnum.DATA_ERROR-> {
mStateViewLiveData.postValue(StateLayoutEnum.DATA_ERROR)
            }
StateLayoutEnum.DATA_NULL-> {
mStateViewLiveData.postValue(StateLayoutEnum.DATA_NULL)
            }
StateLayoutEnum.NET_ERROR-> {
mStateViewLiveData.postValue(StateLayoutEnum.NET_ERROR)
            }
StateLayoutEnum.HIDE-> {
mStateViewLiveData.postValue(StateLayoutEnum.HIDE)
            }
        }
    }
/*** AUTHOR:AbnerMing* INTRODUCE:获取Repository*/inlinefun<reifiedR>getRepository(): R? {
try {
valclazz=R::class.javareturnclazz.newInstance()
        } catch (e: Exception) {
e.printStackTrace()
        }
returnnull    }
}


复杂的Activity,大家可以发现,其实就是标准的MVVM形式封装,Fragment的封装也是基于此,搞清楚上述,基本上我们这个基类库就完成了大半,确实也没什么好说的,大家直接看使用吧。


四、封装后在业务中如何使用


通过以上的封装,我们在业务层所有的页面就可以继承父类进行使用,以达到代码的高度统一,使得架构模式简单化,让业务代码清晰化,目前的封装,大家可以直接封装成库或者打成aar给到其他开发者使用,目前我已经上传到远程,不想麻烦的老铁,可以直接按照下面的步骤进行使用。


1、在你的根项目下的build.gradle文件下,引入maven


allprojects {
repositories {
maven { url"https://gitee.com/AbnerAndroid/almighty/raw/master" }
    }
}


2、在你需要使用的Module中build.gradle文件下,引入依赖。


dependencies {
implementation'com.vip:base:1.0.1'}


通过以上的Maven仓库依赖,我们就可以愉快的进行使用了,下面针对各个封装的功能进行一个简单的演示,当然,大家可以直接看源码中的实例,那里相对比较全面。


1、普通的Activity的继承


如果,你的Activity页面逻辑比较简单,建议继承BaseActivity,此父类,没有与ViewModel相结合,只包含正常且简单的逻辑处理,目前必须重写的只有一个initData方法,其他方法,大家可以根据业务重写即可。


classMainActivity : BaseActivity<ActivityMainBinding>(R.layout.activity_main) {
/*** AUTHOR:AbnerMing* INTRODUCE:初始化数据*/overridefuninitData() {
setBarTitle("主页")
    }
}

2、ViewModel形式Activity的继承

View层,需要继承BaseVMActivity

classTestViewModelActivity : BaseVMActivity<ActivityViewModelBinding,
TestViewModel>(R.layout.activity_view_model) {
overridefuninitVMData() {
setBarTitle("ViewModel方式使用")
    }
}

ViewModel层,需要继承BaseViewModel

classTestViewModel : BaseViewModel() {
/*** AUTHOR:AbnerMing* INTRODUCE:获取需要的Repository*/privatevalrepositorybylazy {
getRepository<TestRepository>()
    }
}

Model层,一般根据实际需要,进行具体的封装使用。

classTestRepository {
}


3、DataBinding形式使用

View层,继承BaseVMActivity,返回当前视图的绑定variable


classDataBindActivity :
BaseVMActivity<ActivityDataBindBinding,
DataBindViewModel>(R.layout.activity_data_bind) {
overridefuninitVMData() {
setBarTitle("DataBinding使用")
    }
overridefungetVariableId(): Int {
returnBR.data    }
}


ViewModel层继承BaseViewModel


classDataBindViewModel : BaseViewModel() {
varoneWayContent="单向绑定数据测试"vartwoWayContent="双向绑定数据测试"/*** AUTHOR:AbnerMing* INTRODUCE:获取双向绑定数据*/varclickListener=View.OnClickListener {
Toast.makeText(it.context, twoWayContent, Toast.LENGTH_SHORT).show()
    }
}


XML视图,直接绑定


<layoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"><data><variablename="data"type="com.abner.base.bind.DataBindViewModel"/></data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:paddingLeft="@dimen/gwm_dp_20"android:paddingRight="@dimen/gwm_dp_20"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_marginTop="@dimen/gwm_dp_20"android:text="@{data.oneWayContent}"/><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="@dimen/gwm_dp_20"android:hint="双向绑定"android:text="@={data.twoWayContent}"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_marginTop="@dimen/gwm_dp_20"android:onClick="@{data.clickListener}"android:text="获取双向绑定数据"/></LinearLayout></layout>


4、Fragment的简单使用


如果,你的Fragment页面逻辑比较简单,建议继承BaseFragment,此父类,没有与ViewModel相结合,只包含正常且简单的逻辑处理,目前必须重写的只有一个initData方法,其他方法,大家可以根据业务重写即可。


classTestPagerFragment : BaseFragment<FragmentTestPagerBinding>(R.layout.fragment_test_pager) {
overridefuninitData() {
    }
}


5、ViewModel形式Fragment的继承


View层,需要继承BaseVMFragment


classTestViewModelPagerFragment :
BaseVMFragment<FragmentTestPagerBinding,
TestFragmentViewModel>(R.layout.fragment_test_pager) {
overridefuninitVMData() {
    }
}


ViewModel层,需要继承BaseViewModel


classTestFragmentViewModel :BaseViewModel(){
/*** AUTHOR:AbnerMing* INTRODUCE:获取需要的Repository*/privatevalrepositorybylazy {
getRepository<TestRepository>()
    }
}


Model层,一般根据实际需要,进行具体的封装使用。


classTestRepository {
}


6、Fragment的DataBinding形式使用和Activity类似,就不赘述了。

7、事件消息总线使用

普通事件发送

LiveDataBus.send("send", "我发送了一条普通消息")

普通发送事件接收

LiveDataBus.observe(this, "send", Observer<String> {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
        })

   

粘性事件发送


LiveDataBus.sendSticky("sendSticky", "我发送了一条粘性事件消息")

 

粘性事件接收


LiveDataBus.observeSticky(this, "sendSticky", Observer<String> {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
        })

 

更多的其他功能使用,大家直接看Github即可,上边有比较清晰的介绍。



五、开源以及Demo查看


以上的封装,目前已经开源,大家可以下载查看源码,或者进行二次更改使用,地址是:

https://github.com/AbnerMing888/VipBase


相关Demo,大家可以down下项目,运行即可,这里简单贴张效果图:


目前的封装,没有过多的冗余代码,完全可以满足实际的业务需要,大家可以按照这种模式试验一番,遇到问题,可以多多交流,毕竟,技术是开放的,交流中才能不断的进步,当然了,需要结合自己的实际业务进行使用,毕竟项目中不应存在多个架构模式,MVC也好,MVPMVVMMVI也罢,无论使用哪种,适合的才是最好的。


好了,各位老铁,这篇文章就到这里,下篇文章《组件化开发,从未如此简单》正在撰写中,大家敬请期待!

相关文章
|
4天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
51 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
4天前
|
安全 API Android开发
Android网络和数据交互: 解释Retrofit库的作用。
Android网络和数据交互: 解释Retrofit库的作用。
41 0
|
4天前
|
XML Android开发 数据安全/隐私保护
Android 自定义开源库 EasyView
Android 自定义开源库 EasyView
|
4天前
|
Unix Linux Shell
FFmpeg开发笔记(八)Linux交叉编译Android的FFmpeg库
在Linux环境下交叉编译Android所需的FFmpeg so库,首先下载`android-ndk-r21e`,然后解压。接着,上传FFmpeg及相关库(如x264、freetype、lame)源码,修改相关sh文件,将`SYSTEM=windows-x86_64`改为`SYSTEM=linux-x86_64`并删除回车符。对x264的configure文件进行修改,然后编译x264。同样编译其他第三方库。设置环境变量`PKG_CONFIG_PATH`,最后在FFmpeg源码目录执行配置、编译和安装命令,生成的so文件复制到App工程指定目录。
51 9
FFmpeg开发笔记(八)Linux交叉编译Android的FFmpeg库
|
1天前
|
前端开发 Android开发
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
|
3天前
|
监控 安全 API
orhanobut/logger - 强大的Android日志打印库
orhanobut/logger - 强大的Android日志打印库
8 1
|
4天前
|
Android开发 C++
Android S HAL库的编译
Android S HAL库的编译
12 0
|
4天前
|
安全 Linux Android开发
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
该文介绍了如何在Linux服务器上交叉编译Android的FFmpeg库以支持HTTPS视频播放。首先,从GitHub下载openssl源码,解压后通过编译脚本`build_openssl.sh`生成64位静态库。接着,更新环境变量加载openssl,并编辑FFmpeg配置脚本`config_ffmpeg_openssl.sh`启用openssl支持。然后,编译安装FFmpeg。最后,将编译好的库文件导入App工程的相应目录,修改视频链接为HTTPS,App即可播放HTTPS在线视频。
29 3
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
|
4天前
|
设计模式 前端开发 数据库
构建高效Android应用:使用Jetpack架构组件实现MVVM模式
【4月更文挑战第21天】 在移动开发领域,构建一个既健壮又易于维护的Android应用是每个开发者的目标。随着项目复杂度的增加,传统的MVP或MVC架构往往难以应对快速变化的市场需求和复杂的业务逻辑。本文将探讨如何利用Android Jetpack中的架构组件来实施MVVM(Model-View-ViewModel)设计模式,旨在提供一个更加模块化、可测试且易于管理的代码结构。通过具体案例分析,我们将展示如何使用LiveData, ViewModel, 和Repository来实现界面与业务逻辑的分离,以及如何利用Room数据库进行持久化存储。最终,你将获得一个响应迅速、可扩展且符合现代软件工
26 0
|
4天前
|
移动开发 前端开发 数据管理
构建高效Android应用:采用MVVM架构与LiveData的全面指南
在移动开发领域,构建一个既快速又可靠的应用对于开发者来说至关重要。随着Android Jetpack组件的推出,MVVM(Model-View-ViewModel)架构和LiveData已成为实现响应式、可测试且易于维护应用的首选解决方案。本文将深入探讨如何在Android应用中实施MVVM模式,以及如何利用LiveData来优化UI组件的数据更新流程,确保用户界面与业务逻辑之间的高度解耦和流畅交互。
22 4