李世德的博客

我有故事,你有酒吗?

0%

Android:Utils 规范&路由导航工具

Utils 规范

  1. 使用 Kotlin
    为什么是 Kotlin?因为 Kotlin 方便扩展某一类 util(使用扩展函数)。

    • 强制:必须注释。
    • 建议:放在基础层的 CommonSDK 里。
  2. 第三方库提供了单例,可以直接调用时,一定不要直接用。 将其调用方法再包一层,降低侵入性。如需更换其他同类框架,在调用函数方面影响小一点(但不是没有影响,因为有些地方还是要改,只是让你改的地方少一点)。

    以 ARouter 为例,ARouter 提供了 ARouter.getInstance()... 的用法,请不要偷懒,要自己再包一层。

使用单例模式

可以使用 Kotlin 的 object 很容易地声明单例模式。

1
2
3
4
5
6
7
8
9
object Singleton {
...
}

// Kotlin 中调用
Singleton.xx()

// Java 中调用
Singleton.INSTANCE.xx()

该类将永远只有一个实例,并且该实例(以线程安全的方式首次访问它时创建)具有与该类相同的名称。文档地址

这种方式和 Java 单例模式的饿汉式一样,不过比 Java 中的实现代码量少很多,其实是个语法糖。

有人说,Utils 推荐使用懒汉式,在使用时再初始化。但是,根据官方文档的意思,object 的单例模式是以线程安全的方式首次访问它时创建的,所以这里是可以用 object 的单例模式来声明的。

工具方法

一般地,都会写多个静态方法来实现不同特性的功能。但是在封装一些第三方库,采用这样的方式真的好吗?别人写的那么好的框架,可使用 Builder 来链式调用,却被装在了固定参数的静态方法里。。。

实践:导航路由器

下面就结合这几点对路由导航工具类进行一个升级优化。我们可以运用 Builder 模式,使用提供给此构建器的参数创建一个自己的导航路由器。

项目中使用 ARouter 进行路由导航,我们可以按照类似参数声明创建自己的构建器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import android.app.Activity
import android.content.Context
import android.os.Bundle
import androidx.annotation.AnimRes
import com.alibaba.android.arouter.facade.Postcard
import com.alibaba.android.arouter.launcher.ARouter

/**
* Navigation to the route with path.
* <p>
* Creates a navigation router with the arguments supplied to this builder.
* <p>
* There will only ever be one instance of this class,
* and the instance (which is created the first time it is accessed,
* in a thread-safe manner) has got the same name as the class.
* <p>
* Used in Kotlin:
* Nav.setPath("").setContext(activity).go()
* <p>
* Used in Java:
* Nav.INSTANCE.setPath("").setContext(activity).go();
*
* @author lishide
* @date 2020/6/18
*/
object Nav {

private var path: String? = null
private var mContext: Context? = null
private var bundle: Bundle? = null
private var flag = -1
private var enterAnim = -1
private var exitAnim = -1
private var requestCode = -1

init {
initialize()
}

private fun initialize() {
path = null
mContext = null
bundle = null
flag = -1
enterAnim = -1
exitAnim = -1
requestCode = -1
}

/**
* Set the path.
*
* @param path Where you go.
* @return This Builder object to allow for chaining of calls to set methods.
* @see ARouter.build(String)
*/
fun setPath(path: String): Nav {
this.path = path
return this
}

/**
* Set the context.
*
* @param context Activity and so on.
* @return This Builder object to allow for chaining of calls to set methods.
*/
fun setContext(context: Context?): Nav {
mContext = context
return this
}

/**
* Set the bundle.
* <p>
* BE ATTENTION TO THIS METHOD WAS <P>SET, NOT ADD!</P>
*
* @param bundle bundle
* @return This Builder object to allow for chaining of calls to set methods.
* @see Postcard.with(Bundle)
*/
fun setBundle(bundle: Bundle?): Nav {
this.bundle = bundle
return this
}

/**
* Set special flags controlling how this intent is handled.
*
* @param flag Flags of route
* @return This Builder object to allow for chaining of calls to set methods.
* @see Postcard.withFlags
*/
fun setFlag(flag: Int): Nav {
this.flag = flag
return this
}

/**
* Set the requestCode.
*
* @param requestCode requestCode
* @return This Builder object to allow for chaining of calls to set methods.
*/
fun setRequestCode(requestCode: Int): Nav {
this.requestCode = requestCode
return this
}

/**
* Set normal transition anim.
*
* @param enterAnim enter
* @param exitAnim exit
* @return This Builder object to allow for chaining of calls to set methods.
* @see Postcard.withTransition
*/
fun setTransition(@AnimRes enterAnim: Int, @AnimRes exitAnim: Int): Nav {
this.enterAnim = enterAnim
this.exitAnim = exitAnim
return this
}

/**
* Creates an {@link ARouter} with the arguments supplied to this builder.
* <p>
* Calling this method navigation to the route with path in postcard.
*/
fun go() {
if (path.isNullOrEmpty()) {
println("path can not be empty")
return
}

val postcard = ARouter.getInstance().build(path)
if (null != bundle) {
postcard.with(bundle)
}
if (flag != -1) {
postcard.withFlags(flag)
}
if (enterAnim != -1 && exitAnim != -1) {
postcard.withTransition(enterAnim, exitAnim)
}
if (null == mContext) {
postcard.navigation()
} else {
if (requestCode != -1 && mContext is Activity) {
postcard.navigation(mContext as Activity?, requestCode, LoginNavigationCallbackImpl())
} else {
postcard.navigation(mContext, LoginNavigationCallbackImpl())
}
}

initialize()
}

}
  • Used in Kotlin:
    Nav.setPath("").setContext(activity).go()

  • Used in Java:
    Nav.INSTANCE.setPath("").setContext(activity).go();

可以在方法上加上 @JvmStatic 注解,在 Java 中使用就和 Kotlin 中一样了。感觉没必要,就多写个 .INSTANCE

这样就将导航路由器封装好了,最终使用了 ARouter 的导航。如果以后要更换其他的路由框架,只修改最后的一个方法就行了,是不是比以前写若干个静态方法要更方便呢。

仅设置了常用的一些方法,更多的方法在使用到的时候追加即可。

LoginNavigationCallbackImpl 是一个登录拦截器路由跳转的回调实现,具体功能实现见:使用 ARouter 实现登录拦截,如不需要,可直接去掉此参数。

类似地,可以给图片加载器 Glide,事件总线 EventBus、MMKV 等 key-value 库等套一层。