1、全局获取Context的技巧
回想一下我们在项目中你会发现有很多地方都需要Context,比如:弹出Toast的时候需要,启动活动的时候需要,发送广播的时候需要,操作数据库的时候需要,使用通知的时候需要,等等等等。
或许目前你还没有为得不到Context而发愁过,因为我们很多的操作都是在活动中进行的,而活动本身就是一个Context对象。但是当应用程序的架构逐渐开始复杂起来的时候,很多的逻辑代码都将脱离Activity类,但此时你又恰恰需要使用Context,也许这个时候你就会感到有些伤脑筋了。
由以上可以看出,在某些情况下,获取Context并非那么容易的一件事,有时候还是挺伤脑筋的,不过别担心,下面我们来学习一种技巧,让你在项目的任何地方都能轻松获取到Context。
Android提供了一个Application类,每当应用程序启动的时候,系统就会自动将这个类进行初始化。而我们可以定制一个自己的Application类,以便于管理程序内一些全局的状态信息,比如说全局Context。
定制一个自己的Application其实并不复杂,首先我们需要创建一个MyApplication类继承自Application,代码如下示例:
public class MyApplication extends Application {
private static Context context;
@Override
public void onCreate(){
context = getApplicationContext();
}
public static Context getContext(){
return context;
}
}
接下来我们需要告知系统,当程序启动的时候应该初始化MyApplication类,而不是默认的Application类。这一步也很简单,在AndroidManifest.xml文件的<application>标签下进行指定就可以了,如下所示:
<application
android:name="MyApplication全限定名"
...>
...
</application>
2、使用Intent传递对象
Intent的用法相信你已经比较熟悉了,我们可以借助它来启动活动、发送广播、启动服务等。在进行上述操作的时候,我们还可以在 Intent 中添加一些附加数据,以达到传值的效果,比如在 FirstActivity 中添加如下代码:
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("string_data", "hello");
intent.putExtra("int_data", 100);
startActivity(intent);
这里调用了 Intent的 putExtra()方法来添加要传递的数据,之后在 SecondActivity 中就可以得到这些值了,代码如下所示:
getIntent().getStringExtra("string_data");
getIntent().getIntExtra("int_data", 0);
但是不知道你有没有发现,putExtra()方法中所支持的数据类型是有限的,虽然常用的一些数据类型它都会支持,但是当你想去传递一些自定义对象的时候就会发现无从下手。不用担心,下面我们就学习一下使用 Intent 来传递对象的技巧。
2.1、Serializable 方式
使用 Intent 来传递对象通常有两种实现方式,Serializable 和 Parcelable,本小节中我们先来学习一下第一种的实现方式。Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。至于序列化的方法也很简单,只需要让一个类去实现 Serializable 这个接口就可以了。比如说有一个 Person 类,其中包含了 name 和 age 这两个字段,想要将它序列化就可以这样写:
public class Person implements Serializable{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
其中 get、set 方法都是用于赋值和读取字段数据的,最重要的部分是在第一行。这里让Person 类去实现了 Serializable 接口,这样所有的 Person 对象就都是可序列化的了。接下来在 FirstActivity 中的写法非常简单:
Person person = new Person();
person.setName("Tom");
person.setAge(20);
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("person_data", person);
startActivity(intent);
可以看到,这里我们创建了一个 Person 的实例,然后就直接将它传入到 putExtra()方法中了。由于 Person 类实现了 Serializable接口,所以才可以这样写。接下来在 SecondActivity 中获取这个对象也很简单,写法如下:
Person person = (Person) getIntent().getSerializableExtra("person_data");
这里调用了 getSerializableExtra()方法来获取通过参数传递过来的序列化对象,接着再将它向下转型成 Person 对象,这样我们就成功实现了使用 Intent 来传递对象的功能了。
2.2、Parcelable 方式
除了 Serializable之外,使用 Parcelable 也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable 方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是 Intent 所支持的数据类型,这样也就实现传递对象的功能了。下面我们来看一下 Parcelable 的实现方式,修改 Person 中的代码,如下所示:
public class Person implements Parcelable {
private String name;
private int age;
……
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name); // 写出name
dest.writeInt(age); // 写出age
}
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
Person person = new Person();
person.name = source.readString(); // 读取name
person.age = source.readInt(); // 读取age
return person;
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}};
}
Parcelable 的实现方式要稍微复杂一些。可以看到,首先我们让 Person 类去实现了Parcelable 接口,这样就必须重写 describeContents()和 writeToParcel()这两个方法。其中describeContents()方法直接返回 0 就可以了,而 writeToParcel()方法中我们需要调用 Parcel的 writeXxx()方法将 Person 类中的字段一一写出。注意字符串型数据就调用 writeString()方法,整型数据就调用 writeInt()方法,以此类推。除此之外,我们还必须在 Person 类中提供一个名为 CREATOR 的常量,这里创建了Parcelable.Creator 接口的一个实现,并将泛型指定为 Person。接着需要重写 createFromParcel()和 newArray()这两个方法,在 createFromParcel()方法中我们要去读取刚才写出的 name 和 age字段,并创建一个 Person 对象进行返回,其中 name 和 age 都是调用 Parcel 的 readXxx()方法读取到的,注意这里读取的顺序一定要和刚才写出的顺序完全相同。而 newArray()方法中的实现就简单多了,只需要 new 出一个 Person 数组,并使用方法中传入的 size 作为数组大小就可以了。接下来在 FirstActivity 中我们仍然可以使用相同的代码来传递 Person 对象,只不过在SecondActivity 中获取对象的时候需要稍加改动,如下所示:Person person = (Person) getIntent().getParcelableExtra("person_data");注意这里不再是调用 getSerializableExtra()方法,而是调用 getParcelableExtra()方法来获取传递过来的对象了,其他的地方都完全相同。这样我们就把使用 Intent 来传递对象的两种实现方式都学习完了,对比一下,Serializable的方式较为简单,但由于会把整个对象进行序列化,因此效率方面会比 Parcelable 方式低一些,所以在通常情况下还是更加推荐使用 Parcelable 的方式来实现 Intent 传递对象的功能。
3、定制自己的日志工具
虽然 Android 中自带的日志工具功能非常强大,但也不能说是完全没有缺点,例如在打印日志的控制方面就做得不够好。打个比方,你正在编写一个比较庞大的项目,期间为了方便调试,在代码的很多地方都打印了大量的日志。最近项目已经基本完成了,但是却有一个非常让人头疼的问题,之前用于调试的那些日志,在项目正式上线之后仍然会照常打印,这样不仅会降低程序的运行效率,还有可能将一些机密性的数据泄露出去。那该怎么办呢,难道要一行一行把所有打印日志的代码都删掉?显然这不是什么好点子,不仅费时费力,而且以后你继续维护这个项目的时候可能还会需要这些日志。因此,最理想的情况是能够自由地控制日志的打印,当程序处于开发阶段就让日志打印出来,当程序上线了之后就把日志屏蔽掉。看起来好像是挺高级的一个功能,其实并不复杂,我们只需要定制一个自己的日志工具就可以轻松完成了。比如新建一个 LogUtil 类,代码如下所示:
public class LogUtil {
public static final int VERBOSE = 1;
public static final int DEBUG = 2;
public static final int INFO = 3;
public static final int WARN = 4;
public static final int ERROR = 5;
public static final int NOTHING = 6;
public static final int LEVEL = VERBOSE;
public static void v(String tag, String msg) {
if (LEVEL <= VERBOSE) {
Log.v(tag, msg);
}
}
public static void d(String tag, String msg) {
if (LEVEL <= DEBUG) {
Log.d(tag, msg);
}
}
public static void i(String tag, String msg) {
if (LEVEL <= INFO) {
Log.i(tag, msg);
}
}
public static void w(String tag, String msg) {
if (LEVEL <= WARN) {
Log.w(tag, msg);
}
}
public static void e(String tag, String msg) {
if (LEVEL <= ERROR) {
Log.e(tag, msg);
}
}
}
可以看到,我们在 LogUtil 中先是定义了 VERBOSE、DEBUG、INFO、WARN、ERROR、NOTHING 这六个整型常量,并且它们对应的值都是递增的。然后又定义了一个 LEVEL 常量,可以将它的值指定为上面六个常量中的任意一个。接下来我们提供了 v()、d()、i()、w()、e()这五个自定义的日志方法,在其内部分别调用了 Log.v()、Log.d()、Log.i()、Log.w()、Log.e()这五个方法来打印日志,只不过在这些自定义的方法中我们都加入了一个 if 判断,只有当 LEVEL 常量的值小于或等于对应日志级别值的时候,才会将日志打印出来。这样就把一个自定义的日志工具创建好了,之后在项目里我们可以像使用普通的日志工具一样使用 LogUtil,比如打印一行 DEBUG 级别的日志就可以这样写:
LogUtil.d("TAG", "debug log");
打印一行 WARN 级别的日志就可以这样写:
LogUtil.w("TAG", "warn log");
然后我们只需要修改 LEVEL 常量的值,就可以自由地控制日志的打印行为了。比如让LEVEL 等于 VERBOSE 就可以把所有的日志都打印出来,让 LEVEL 等于 WARN 就可以只打印警告以上级别的日志,让 LEVEL 等于 NOTHING 就可以把所有日志都屏蔽掉。使用了这种方法之后,刚才所说的那个问题就不复存在了,你只需要在开发阶段将LEVEL 指定成 VERBOSE,当项目正式上线的时候将 LEVEL 指定成 NOTHING 就可以了。