最近在接手项目遇到模块化开发,jni,网络请求签名,加密,Https等等技术栈,在这里我就简单说下,Arouter阿里路由实现组件化开发,以及利用cmake配置实现jni本地交互.
1.Arouter
对于大型点的项目来说,模块化开发是必不可少的,他不仅仅方便迭代,维护,也适合团队开发,各自开发各自对应的模块,那么模块化开发我们怎么实现模块之间跳转,数据交互呐?有的小伙伴在想直接startactivity,利用bundle传递不就行了?
回答这个问题之前我举个例子:我在as创建两个子模块分别A,B。假如B模块依赖A模块,那么相当于在B模块中可以调用A模块的资源(包括类,第三方库,页面跳转,activity数据传递),问题来了。那么如果我需要在A模块中调用B模块资源呐?是不是要在gradle配置A模块依赖B模块?好吧就这么干。当你依赖完成后编译as突然就报错了,没错,就是报错了,因为A,B两个模块相互依赖是不允许的.有人问为什么两个模块之间不能相互依赖?我们可以想一想如果两个模块相互依赖那么我们分模块还有什么意义,所有资源都可以互相调用。好了不纠结这个问题,言而总之模块之间不能相互依赖。
那么既然模块之间不能相互依赖A,B模块之间怎么互调或者说传递数据呐?这时候就是我们今天的主角之一:Arouter即阿里路由。
1.1Arouter作用及使用意义
上面说到两个子模块跳转,传递数据通过相互依赖是不行的,那么Arouter阿里路由就为我们解决了此问题,集成阿里路由可以实现同一进程的任何两个模块页面相互跳转,相互传递数据;
1.2Arouter的集成
由于本人用的Kotlin语言开发,我就说说kotlin集成Arouter,java语言集成与Kotlin稍微有点不同,如果需要可自行查看官网集成配置;
第一步:打开gradle配置:
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
api 'com.alibaba:arouter-api:1.5.0'
kapt 'com.alibaba:arouter-compiler:1.2.2'
第二步:所有子模块gradle配置:
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
kapt 'com.alibaba:arouter-compiler:1.2.2'
如果你项目中主模块配置完成了,其他子模块都依赖这个主模块,那么在子模块就可以省略依赖arouter(api 'com.alibaba:arouter-api:1.5.0')这一步,特别注意:两个模块依赖时要用api方式引入
1.3Arouter的简单实用
application里面初始化Arouter:
if (BuildConfig.DEBUG) {
ARouter.openLog() // 打印日志
ARouter.openDebug() // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(this)
阿里路由主要通过注解的方式,配置路由表进行页面直接跳转;
先看看路由表所有跳转的页面都需要配置在这里面:
/**
* Created by Android Studio.
* User: Lenovo
* Date: 2019/11/5
* Time: 12:41
* 路由表
*/
class ARouterPath {
companion object {
const val LOGIN_PATH = "/user/loginactivity"
const val MAIN_PATH = "/baseproject/mainactivity"
const val DOCTOR_PATH = "/doctor/doctoractivity"
}
}
然后在路由表配置的对应activtiy添加注解:比如上面的 const val LOGIN_PATH = "/user/loginactivity"
@Route(path = ARouterPath.LOGIN_PATH)
class LoginActivity : BaseActivity()
最后我需要从Mainactivity跳转到上面注解的LoginActivity
ARouter.getInstance().build(ARouterPath.LOGIN_PATH)
.navigation()
调用Arouter的api,在build里面传入路由表里面的配置.OK一个简单的Arouter路由跳转就完事了.
1.4Arouter实现页面跳转传参:
页面传参主要注意一下首先在接收参数页面添加Arouter注入
//阿里路由注入
ARouter.getInstance().inject(this)
那么有注入就有注销了?是不是在ondestory()里面
ARouter.getInstance().destroy()就可以了?答案是否定的,至于具体原因还没弄清楚.
注销网上都建议放到application里面的(如果自己定义了application就写在自定义application里面)
override fun onTerminate() {
super.onTerminate()
ARouter.getInstance().destroy()
}
好了回归正题:我需要从doctoractivity传递参数到loginactivity(随便举例子类名不要在意那么多)
@Route(path = ARouterPath.DOCTOR_PATH)
class DoctorActivity : BaseActivity(){
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_doctor)
doctor_txt.setOnClickListener {
ARouter.getInstance().build(ARouterPath.LOGIN_PATH)
.withParcelable("doctor", BaseArouterDataClass("a", "b", "c"))
.navigation()
finish()
}
}
@Route(path = ARouterPath.LOGIN_PATH)
class LoginActivity : BaseActivity() {
@Autowired(name = "doctor")
@JvmField
var doctor: BaseArouterDataClass? = null
var mAlertDialog: mAlertDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
//阿里路由注入
ARouter.getInstance().inject(this)
doctor?.let {
Log.e("doctor", doctor!!.a + "///" + doctor!!.b + "///" + doctor!!.c)
}
login_txt.setOnClickListener {
ARouter.getInstance().build(ARouterPath.DOCTOR_PATH)
.navigation()
finish()
}
}
关注两个点:(1)传参的时候在doctoractivity里面 .withParcelable("doctor", BaseArouterDataClass("a", "b", "c"))这里传入了一个实现Parcelable序列化的对象,当然我们也可以传递string,int,float,boolean等等类型的数据.
(2)在接手参数页面loginactivtiy里面我们看到有两个注解第一个是Autowired是Arouter接收参数的注解,第二个JvmField是kotlin中使用Arouter接收参数需要的注解,这里参数的变量名一定要与传参的时候key值保持一致,不然接收不到数据.
2.配置cmake实现jni本地交互:
目前as创建项目的时候支持自带的cmake配置的jni交互demo,具体代码我就不cv出来了,我就说下思路,
首先有个CmakeLists.txt里面配置了cmake版本,以及我们要加载的本地cpp代码,然后在gradle里面配置cmake
defaultConfig{
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
然后我们在java代码里面动态加载cpp对应的库,定义一个native方法调用本地cpp里面对应的方法.
class SecretUtil {
init {
System.loadLibrary("native-lib")
}
companion object {
val getInstance: SecretUtil by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
SecretUtil()
}
}
external fun stringFromJNI(): ByteArray
}
cpp文件及里面的代码我就不贴出来了。就这样一套简单的jni交互就ok了(不清楚的可以创建一个项目选址c++也就是最后一个选项看看demo)。
拓展:
1.一个cmake配置多个cpp文件,
cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp)
add_library( # Sets the name of the library.
abc-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
abc-lib.cpp
)
#第二个so
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
target_link_libraries(native-lib ${log-lib})
#第二个so
target_link_libraries(abc-lib ${log-lib})
直接在cmake里面加一个add_library。其中native-lib, abc-lib就是我创建的两个cpp文件,编译成功后生成的so文件就是
libnative-lib.so,libabc-lib.so
2.如何在java中直接调用so库里面方法从而删除cpp文件:
删除cpp,直接调用生成的so文件里面的方法,这时候我们就要想到,如果删除cpp文件那么我们cmake配置是不是就不起效果了,cmake配置不起效果是不是上面说的gradle配置cmake相关也不起效果了,因此我们首先干掉cmake,然后注释掉gradle配置的cmake,然后as切换project模式,找到buidle目录复制里面的arm(里面含有so文件),放到libs目录里面,最后我们在gradle里面指定加载libs文件
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
然后我们就可以编译运行了。这样我们在java调用native方法就直接调用so库里面的,从而删掉本地cpp里面的c代码.这样别人连你cpp里面c的代码具体实现都不知道,是不是显得逼格更高...