Android高级技巧
高级技巧
全局获取Context的技巧
回想这么久以来我们所学的内容,你会发现有很多地方都需要用到Context,弹出Toast的时候 需要,启动Activity的时候需要,发送广播的时候需要,操作数据库的时候需要,使用通知的时 候需要……
或许目前你还没有为得不到Context而发愁过,因为我们很多的操作是在Activity中进行的, 而Activity本身就是一个Context对象。但是,当应用程序的架构逐渐开始复杂起来的时候, 很多逻辑代码将脱离Activity类,但此时你又恰恰需要使用Context,也许这个时候你就会 感到有些伤脑筋了。
例如,在第12章的Kotlin课堂中,我们编写了一个Toast.kt文件,并在这里对Toast的用法进行 了封装,代码如下所示:
fun String.showToast(context: Context, duration: Int = Toast.LENGTH_SHORT){ |
可以看到,由于Toast的makeText()方法要求传入一个Context参数,但是当前代码既不在 Activity当中,也不在Service当中,是没有办法直接获取Context对象的。于是这里我们只好 给showToast()方法添加了一个Context参数,让调用showToast()方法的人传递一个 Context对象进来。
虽说这也是一种解决方案,但是有点推卸责任的嫌疑,因为我们将获取Context的任务转移给 了showToast()方法的调用方,至于调用方能不能得到Context对象,那就不是我们需要考虑 的问题了。
由此可以看出,在某些情况下,获取Context并非是那么容易的一件事,有时候还是挺伤脑筋 的。不过别担心,下面我们就来学习一种技巧,让你在项目的任何地方都能够轻松获取 Context。
Android提供了一个Application类,每当应用程序启动的时候,系统就会自动将这个类进行 初始化。而我们可以定制一个自己的Application类,以便于管理程序内一些全局的状态信 息,比如全局Context。
定制一个自己的Application其实并不复杂,首先需要创建一个MyApplication类继承自 Application,代码如下所示:
class MyApplication: Application(){ |
可以看到,MyApplication中的代码非常简单。这里我们在companion object中定义了一 个context变量,然后重写父类的onCreate()方法,并将调用
getApplicationContext()方法得到的返回值赋值给context变量,这样我们就可以以静 态变量的形式获取Context对象了。
需要注意的是,将Context设置成静态变量很容易会产生内存泄漏的问题,所以这是一种有风 险的做法。
但是由于这里获取的不是Activity或Service中的Context,而是Application中的Context, 它全局只会存在一份实例,并且在整个应用程序的生命周期内都不会回收,因此是不存在内存 泄漏风险的。那么我们可以使用如下注解,让Android Studio忽略上述警告提示:
class MyApplication: Application(){ |
接下来我们还需要告知系统,当程序启动的时候应该初始化MyApplication类,而不是默认的 Application类。这一步也很简单,在AndroidManifest.xml文件的标签下 进行指定就可以了,代码如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
这样我们就实现了一种全局获取Context的机制,之后不管你想在项目的任何地方使用 Context,只需要调用一下MyApplication.context就可以了。
那么接下来我们再对showToast()方法进行优化,代码如下所示:
fun String.showToast(duration: Int = Toast.LENGTH_SHORT) { |
可以看到,showToast()方法不需要再通过传递参数的方式得到Context对象,而是调用一下 MyApplication.context就可以了。这样showToast()方法的用法也得到了进一步的精 简,现在只需要使用如下写法就能弹出一段文字提示:
"This is Toast".showToast() |
有了这个技巧,你就再也不用为得不到Context对象而发愁了。
使用Intent传递对象
Intent的用法相信你已经比较熟悉了,我们可以借助它来启动Activity、启动Service、发送广 播等。在进行上述操作的时候,我们还可以在Intent中添加一些附加数据,以达到传值的效 果,比如在FirstActivity中添加如下代码:
val intent = Intent(this, SecondActivity::class.java) |
这里调用了Intent的putExtra()方法来添加要传递的数据,之后在SecondActivity中就可 以得到这些值了,代码如下所示:
intent.getStringExtra("string_data") |
但是不知道你有没有发现,putExtra()方法中所支持的数据类型是有限的,虽然常用的一些 数据类型是支持的,但是当你想去传递一些自定义对象的时候,就会发现无从下手。不用担 心,下面我们就学习一下使用Intent来传递对象的技巧。
Serializable方式
使用Intent来传递对象通常有两种实现方式:Serializable和Parcelable。本小节中我们先来学 习一下第一种实现方式。
Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象 可以在网络上进行传输,也可以存储到本地。至于序列化的方法非常简单,只需要让一个类去 实现Serializable这个接口就可以了。
比如说有一个Person类,其中包含了name和age这两个字段,如果想要将它序列化,就可以这 样写:
class Person : Serializable { |
这里我们让Person类实现了Serializable接口,这样所有的Person对象都是可序列化的 了。
然后在FirstActivity中只需要这样写:
val person = Person() |
可以看到,这里我们创建了一个Person的实例,并将它直接传入了Intent的putExtra()方法 中。由于Person类实现了Serializable接口,所以才可以这样写。
接下来在SecondActivity中获取这个对象也很简单,写法如下:
val person = intent.getSerializableExtra("person_data") as Person |
这里调用了Intent的getSerializableExtra()方法来获取通过参数传递过来的序列化对 象,接着再将它向下转型成Person对象,这样我们就成功实现了使用Intent传递对象的功能。
需要注意的是,这种传递对象的工作原理是先将一个对象序列化成可存储或可传输的状态,传 递给另外一个Activity后再将其反序列化成一个新的对象。虽然这两个对象中存储的数据完全一 致,但是它们实际上是不同的对象,这一点希望你能了解清楚。
Parcelable方式
除了Serializable之外,使用Parcelable也可以实现相同的效果,不过不同于将对象进行序列 化,Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是 Intent所支持的数据类型,这样就能实现传递对象的功能了。
下面我们来看一下Parcelable的实现方式,修改Person中的代码,如下所示:
class Person: Parcelable{ |
Parcelable的实现方式要稍微复杂一些。可以看到,首先我们让Person类实现了Parcelable接口,这样就必须重写describeContents()和writeToParcel()这两个方法。其中descerbrContents()方法直接返回0就可以了,而在writeToParcel()方法中,我们需要调用Parcel的writeXxx()方法,将Person类中的字段一一写出。注意,字符串型数据就调用writeString()方法,整型数据就调用writeInt()方法,以此类推。
除此之外,我们还必须在Person类中提供一个名为CREATOR的匿名类实现。这里创建了 Parcelable.Creator接口的一个实现,并将泛型指定为Person。接着需要重写 createFromParcel()和newArray()这两个方法,在createFromParcel()方法中,我们 要创建一个Person对象进行返回,并读取刚才写出的name和age字段。其中name和age都是 调用Parcel的readXxx()方法读取到的,注意这里读取的顺序一定要和刚才写出的顺序完全 相同。而newArray()方法中的实现就简单多了,只需要调用arrayOfNulls()方法,并使用 参数中传入的size作为数组大小,创建一个空的Person数组即可。
接下来,在FirstActivity中我们仍然可以使用相同的代码来传递Person对象,只不过在 SecondActivity中获取对象的时候需要稍加改动,如下所示:
val person = intent.getParcelableExtra("person_data") as Person |
注意,这里不再是调用getSerializableExtra()方法,而是调用 getParcelableExtra()方法来获取传递过来的对象,其他的地方完全相同。
不过,这种实现方式写起来确实比较复杂,为此Kotlin给我们提供了另外一种更加简便的用法, 但前提是要传递的所有数据都必须封装在对象的主构造函数中才行。
修改Person类中的代码,如下所示:
|
没错,就是这么简单。将name和age这两个字段移动到主构造函数中,然后给Person类添加一 个@Parcelize注解即可,是不是比之前的用法简单了好多倍?
没错,就是这么简单。将name和age这两个字段移动到主构造函数中,然后给Person类添加一 个@Parcelize注解即可,是不是比之前的用法简单了好多倍?
定制自己的日志工具
并不复杂,我们只需要定制一个自己的日志工具就可以 轻松完成了。新建一个LogUtil单例类,代码如下所示:
object LogUtil { |
可以看到,我们在LogUtil中首先定义了VERBOSE、DEBUG、INFO、WARN、ERROR这5个整型 常量,并且它们对应的值都是递增的。然后又定义了一个静态变量level,可以将它的值指定 为上面5个常量中的任意一个。
接下来,我们提供了v()、d()、i()、w()、e()这5个自定义的日志方法,在其内部分别调用 了Log.v()、Log.d()、Log.i()、Log.w()、Log.e()这5个方法来打印日志,只不过在这 些自定义的方法中都加入了一个if判断,只有当level的值小于或等于对应日志级别值的时 候,才会将日志打印出来。
这样就把一个自定义的日志工具创建好了,之后在项目里,我们可以像使用普通的日志工具一 样使用LogUtil。比如打印一行DEBUG级别的日志可以这样写:
LogUtil.d("TAG", "debug log") |
打印一行WARN级别的日志可以这样写:
LogUtil.w("TAG", "warn log") |
我们只需要通过修改level变量的值,就可以自由地控制日志的打印行为。比如让level等于 VERBOSE就可以把所有的日志都打印出来,让level等于ERROR就可以只打印程序的错误日 志。
。 使用了这种方法之后,刚才所说的那个问题也就不复存在了,你只需要在开发阶段将level指 定成VERBOSE,当项目正式上线的时候将level指定成ERROR就可以了。