前言

本章,小编来介绍一些省时省力的开发技巧,都是从前辈那学来的。平时做些小demo还感觉不到这些技巧的实用性,当遇到大项目时就感觉真香。

全局获取Context的技巧

我们在Android开发中难免会用到context,到底传哪个context呢?在Activity这样的类中可以传this(Activity自己就是个context),在自己创建的类中通过外部传入context,那这个context是谁?是Activity?最好不要这样,除非是涉及到跳转(这种情况只能用Activity),为什么呢?先看代码技巧,我再娓娓道来。

Android提供了一个Application类,每当程序启动的时候,系统就会自动将这个类进行初始化。而我们可以定制一个自己的Application类,以便于管理程序内一些全局的状态信息,比如全局Context。

class SkinApplication: Application() {
companion object{
@SuppressLint("StaticFieldLeak")
lateinit var context:Context
}

override fun onCreate() {
super.onCreate()
context = applicationContext
}
}

需要注意的是,将context设置成静态变量很容易导致内存泄漏,所以这是一种有风险的做法,因此Android Studio会给出警告。

但是!由于这里获取的不是Activity或Service中的context,而是Application中的context,它在全局只会存在一份实例,并且在整个应用程序的声明周期内都不会回收,因此是不存在内存泄漏风险的。所以我上面加了个注解@SuppressLint("StaticFieldLeak")让Android Studio忽略上述的警告提示。前面提到最好不要使用Activity的context,现在知道为什么了吧!

当然,别忘了在AndroidManifest.xml中注册创建的Application。

定制日志工具

Android Studio自带的日志工具非常好用,但也存在一些缺点。

我们在做大型项目时肯定会用到不少打印信息,但是当我们完成项目后,之前用于调试的那些日志,在项目上线后仍然会照常打印,这样不仅会降低程序的运行效率,还有可能将一些机密的数据泄漏出去。怎么办?

我们就需要定制日志工具,使我们能够自由控制日志的打印。其实很简单,如下:

创建一个LogUtil单例类

/**
*@Description
*@Author PC
*@QQ 1578684787
*/
object LogUtil {
private const val VERBOSE = 1
private const val DEBUG = 2
private const val INFO = 3
private const val WARN = 4
private const val ERROR = 5
private var level = VERBOSE
fun v(tag:String,msg:String){
if (level <= VERBOSE){
Log.v(tag,msg)
}
}

fun d(tag: String,msg: String){
if (level <= DEBUG){
Log.d(tag, msg)
}
}

fun i(tag: String,msg: String){
if (level <= DEBUG){
Log.i(tag, msg)
}
}

fun w(tag: String,msg: String){
if (level <= DEBUG){
Log.w(tag, msg)
}
}

fun e(tag: String,msg: String){
if (level <= DEBUG){
Log.e(tag, msg)
}
}
}

我们只需要通过修改level变量的值,就可以自由地控制日志的打印行为。比如让level等于VERBOSE就可以把所有的日志都打印出来,让level等于ERROR就可以只打印程序的错误日志。

使用这种方法之后,刚才所说的那个问题就不复存在了,你只需要在开发阶段将level指定成VERBOSE,当项目正式上线的时候将level指定成ERROE就可以了。

使用Intent传递对象

Intent 的用法相信大家都很熟悉,我们可以借助它来启动Activity、Service、发送广播等。在进行上述操作的时候,我们还可以在Intent中添加一些附加数据,以达到传值的效果,比如:

val intent = Intent(this,SecondActivity::class.java).apply { 
putExtra("data","hello")
putExtra("second_data","kotlin")
}
startActivity(intent)

SecondActivity中接收数据

intent.getStringExtra("data")
intent.getIntExtra("second_data",0)

但是,不知道你发现没有,putExtra方法支持的数据类型是有限的,虽然常用的一些数据类型是受支持的,但是当你想去传递一些自定义对象的时候,就会发现无从下手。下面我们就来看看如何下手。

Serializable方式

使用Intent来传递对象通常有两种实现方式:Serializable和Parcelable。

Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。至于序列化的方法,非常简单!只需要让一个类去实现Serializable这个接口就可以了。

class Person :Serializable{
var id = 0
var name = ""
}
 val student = Student().apply {
id = 1
name = "joker"
}
val intent = Intent(this,SecondActivity::class.java).apply {
putExtra("data","kotlin")
putExtra("second_data",200)
putExtra("student_1",student)
}
startActivity(intent)

可以看到,这里我们创建了一个Student的实例,并将它直接传入了Intent的putExtra()方法中 。由于Student类实现了Serializable接口,所以才可以这样写。

SecondActivity中接收数据

val student = intent.getSerializableExtra("student_1") as Student

这里调用了Intent的getSerializableExtra()方法来获取通过参数传递过来的序列化对象,接着再将它向下转型成Student对象,这样我们就成功实现了使用Intent传递对象的功能。

需要注意的是,这种传递对象的工作原理是将一个对象序列化成可存储或可传输的状态,传递给另外一个Activity后再将其反序列化成一个新的对象虽然这两个对象中存储的数据完全一致,但是它们实际上是不同的对象。

Parcelable方式

除了Serializable之外,使用Percelable也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable方式的实现原理是将一个完成的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样就能实现传递对象的功能了。

接下来我们来看一下Parcelable的实现方式:

/**
*@Description
*@Author PC
*@QQ 1578684787
*/
class Student :Parcelable{
var id = 0
var name = ""

override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)//写出name
parcel.writeString(name)//写出age
}

override fun describeContents(): Int {
return 0
}

companion object CREATOR : Parcelable.Creator<Student> {
override fun createFromParcel(parcel: Parcel): Student {
val student = Student()
student.id = parcel.readInt()//读取id
student.name = parcel.readString()?:""//读取name
return student
}

override fun newArray(size: Int): Array<Student?> {
return arrayOfNulls(size)
}
}
}

Parcelable的实现方式要稍微复杂一些。可以看到,首先我们让Student类实现了Parcelable接口,这样就必须重写describeContents()writeToParcel()方法。其中describeContents()方法直接返回0就可以了,而在writeToParcel()方法中,我们需要调用Parcel的writeXxx()方法,将Student类中的字段一一写出。注意,字符串型数据就调用writeString()方法,整型数据就调用writeInt()方法,以此类推。

除此之外,我们还必须在Student类中提供一个名为CREATOR的匿名类实现。这里创建了Parcelable.Creator接口的一个实现。并将泛型指定为Student。接着需要重写createFromParcel()newArray()这两个方法,在createFromParcel()方法中,我们要创建一个Student对象进行返回,并读取刚才写出的id和name字段。其中id和name都是调用Parcel的readXxx()方法读取到的,注意这里读取的顺序一定要和刚才写出的顺序完全相同。而newArray()方法中的实现就简单多了,只需要调用arrayOfNulls()方法,并使用参数中传入的size作为数组大小,创建一个空的Student数组即可。

发送数据的方式相同,我们来看接收数据:

val student = intent.getParcelableExtra<Student>("student_1")

有没有觉得上面这种方式太复杂了,这不像kotlin开发Android的风格。为此Kotlin给我们提供了另外一种更加简便的用法,但前提是传递的所有数据都必须封装在对象的主构造函数中才行。

修改一下Student类的代码:

@Parcelize
class Student(var id:Int,var name:String) : Parcelable

就是这么简单!将id和name这两个字段移动到主构造函数中,然后给Student类添加一个@Parcelize注解即可,是不是比之前的用法简——单——多——了——。

这样我们就把使用Intent传递对象的两种实现方式都学习完了。对比一下,Serializable方式更简单,但由于会把整个对象进行序列化,因此效率会比Parcelable方式低一些(其中还涉及反射产生很多临时变量),所以在通常情况下,还是更加推荐使用Parcelable的方式来实现Intent传递对象的功能。