这一章学到的:
- 全局获取Context的技巧
- 使用Intent传递对象
- Serialiable方式
- Parcelable方式
- 定制自己的日志工具
- 调试Android程序

13.1 全局获取Context的技巧
当应用程序的架构逐渐开始复杂起来的时候,很多的逻辑代码都将脱离Activity类,但此时你又恰恰需要使用Context,也许这个时候你就会感到有些伤脑经了。
在某些情况下,获取Context并非是那么一件事,下面我们就来学习一种技巧,让你在项目的任何地方都能够轻松获取到Context。
Android提供了一个Application类,每当应用程序启动的时候,系统就会自动将这个类进行初始化。而我们可以定制一个自己的Aoolication类,以便于管理程序内一些全局的状态信息,比如说全局Context。
定制一个自己的Application其实并不困难,首先我们需要创建一个MyApplication类继承自Application。
代码如下:
public class MyApplication extends Application
{
private static Context mContext;
@Override
public void onCreate()
{
mContext = getApplicationContext();
}
public static Context getContext()
{
return mContext;
}
}
这里我们重写了父类的onCreate()方法,并通过调用getApplicationContext()方法得到了一个应用程序级别的Context,然后又提供了一个静态的getContext()方法,在这里将刚才获取到的Context进行返回。
注意:getContext()返回的也要是一个static的变量,所以mContext要加static。
接下来我们需要告知系统,当程序启动的时候应该初始化MyApplication类,而不是默认的Application类。这一步也很简单,在AndroidManifest.xml文件的<application>标签下进行指定就可以了。
代码如下所示:
<application
········
android:name="com.example.wumen.getcontext.MyApplication">
·········
</application>
注意这里在指定MyApplication的时候,一定要加上完整的包名,不然系统将无法找到这个类。
这样我们就已经实现了一种全局获取Context的机制,之后不管你想在项目的任何地方使用Context,只需要调用一下MyApplication.getContext()就可以了。
任何一个项目都只能配置一个Application。
13.2 使用Intent传递对象
我们可以借助Intent来启动活动,发送广播,启动服务等。在进行上述操作的时候,我们还可以在Intent中添加一些附加数据,已达到传值的效果。
putExtra()方法中所支持的数据类型是有限的,虽然常用的一些数据类型它都会支持,但是当你想去传递一些自定义对象的时候,就会发现无从下手。
13.2.1 Serializable方式(谁瑞来日报)
Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。至于序列化的方法也很简单,只需要让一个类去实现Serializable这个借口就可以了。
比如说Person类:
public class Person implements Serializable
{
private String name;
private int age;
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
让Person类去实现了Serializable接口,这样所有的Person对象就都是可序列化的了。
Person person = new Person();
person.setName("Tom");
person.setAge(20);
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
intent.putExtra("person_data",person);
startActivity(intent);
这里我们创建了一个Person的实例,然后就直接将它传入到putExtra()方法中了。由于Person类实现了Serializable接口,所以才可以这样写。
Intent intent = getIntent();
Person person = (Person) intent.getSerializableExtra("person_data");
这里调用了getSerializableExtra()方法来获取通过参数传递过来的序列化对象,接着再将它向下转型成Person对象,这样我们就成功实现了使用Intent来传递对象的功能了。
13.2.2 Parcelable方式(扑瑞赛了包)
不同于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了。
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 Creator<Person> CREATOR = new Creator<Person>()
{
@Override
public Person createFromParcel(Parcel in)
{
Person person = new Person();
person.name = in.readString();//读取name
person.age = in.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作为数组大小就可以了。
Person person = new Person();
person.setName("Tom");
person.setAge(20);
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
intent.putExtra("person_data",person);
startActivity(intent);
我们仍然可以使用相同的代码来传递Person对象,只不过在获取对象的时候需要稍加改动。
Intent intent = getIntent();
Person person = (Person) intent.getParcelableExtra("person_data");
对比一下,Serializable的方式较为简单,但由于会把整个对象进行序列化,因此效率会比 Parcelable方式低一些,所以在通常情况下还是更加推荐使用 Parcelable的方式来实现Intent传递对象的功能。
13.3 定制自己的日志工具
最理想的情况是能够自由地控制日志的打印,当程序处于开发阶段时就让日志打印出来,当程序上线了之后就把日志屏蔽掉。
看起来好像是挺高级的一个功能,其实并不复杂,我们只需要定制一个自己的日志工具就可以轻松完成了。比如新建一个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这6个整形常量,并且它们对应的值都是递增的。然后又定义了一个静态变量level,可以将它的值指定为上面6个常量中的任意一个。
接下里我们提供了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");
然后我们只需要修改level变量的值,就可以自由地控制日志的打印行为了。比如让level等于VERBOSE就可以把所有的日志都打印出来,让level等于WARN就可以只打印警告以上级别的日志,让level等于NOTHING就可以把所有日志都屏蔽掉。
13.4 调试Android程序
当开发过程中遇到一些奇怪的bug,但又迟迟定位不出来原因是什么的时候。最好的解决办法就是调试了。调试允许我们逐行地执行代码,并可以实时观察内存中的数据,从而能够比较轻易地查出问题的原因。
调试工作的第一步肯定是添加断点,这里由于我们要调试登陆部分的问题,所以断点可以加在登录按钮的点击时间里。添加断点的方法也很简单,只需要在相应代码行的左边点击一下就可以了。

如果想要取消这个断点,对着它再次点击就可以了。
添加好了断点,接下来就可以对程序进行调试了,点击Android Studio顶部工具栏中的Debug按钮(最右边的按钮),就会使用调试模式来启动程序。

在输入框中输入账号和密码,并点击Login按钮,这时Android Studio就会自动打开Debug窗口。

接下来每按一次F8键,代码就会向下执行一行,并且通过Variables视图还可以看到内存中的数据。

调试完之后点击Debug窗口中的Stop按钮(最下边的按钮)来结束调试即可。

这种调试方式虽然完全可以正常工作,但在调试模式下,程序的运行效率将会大大地降低,如果你的断点加在一个比较靠后的位置,需要执行很多的操作才能运行到这个断点,那么前面这些操作就都会有一些卡顿的感觉。
Android还提供了另外一种调试的方式,可以让程序随时进入到调试模式。
这次不需要选择调试模式来启动程序了,就使用正常的方式来启动程序。把账号和密码输入好,然后点击Android Studio顶部工具栏的Attach debugger to Android process按钮(最左边的按钮)

此时会让弹出一个进程选择提示框

这里目前只列出了一个进程,也就是我们当前程序的进程,选中这个进程,然后点击Ok按钮,就会让这个进程进入到调试模式了。

接下来在程序中点击Login按钮,Android Studio同样也会自动打开Debug窗口,之后的流程就都是相同的了。