一、全局获取Context的技巧:
当应用程序的架构逐渐复杂的时候,许多逻辑代码会脱离Activity类,此时Context的获取就不太方便。但Android提供了一个Application类,每当应用程序启动的时候,系统就会自动将这个类进行初始化,而我们可以定制一个自己的Application类,以便管理程序内的一些全局状态信息,比如说全局Context。
自定义Application的步骤:
(1)首先创建一个MyApplication类继承自Application:
public class MyApplication extends Application {
private static Context context;
@Override
public void onCreate() {
super.onCreate();
//获取context
context = getApplicationContext(); //通过getApplicationContext()方法获取应用系统级别的Context
}
//创建一个静态的方法,以便获取context对象
public static Context getContext(){
return context;
}
}
(2)告知系统当程序启动的时候应该初始化MyApplication类而不是默认的Application类。在AndroidManifest文件中的<application>标签下进行指定:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicetest">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name="com.example.<packagename>.MyApplication" //这里必须是完整的包名
...>
...
</application>
</manifest>
(3)前2步实现了一种全局获取Context的机制,之后不管在任何地方使用Context,只需要调用一下MyApplication.getContext()方法即可。
二、使用Intent传递对象:
Intent可以传递一些数据,但传递的数据类型是有限的,当我们想传递一些自定义对象的时候,就需要使用特殊的方式:
(1)Serializable方式:
Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。序列化的方法:
a.定义一个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;
}
}
b.在主活动代码里:
Person person = new Person();
person.setName("Tom");
person.setAge(20);
Intent intent = new Intent(FirstAcitivity.this,SecondActivity.class);
intent.putExtra("person_data",person); //将实例传入
startActivity(intent);
c.在跳转的活动代码里:
Person person = (Person) getIntent().getSerializableExtra("person_data");
通过调用getSerializableExtra()方法来获取参数传递过来的序列化对象,接着再将它向下转型成Person对象。
(2)Parcelable方式:
Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型。
a.定义一个Person类,包含name和age两个字段。
public class Person implements Parcelable{
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;
}
@Override
public int describeContents() { //实现Parcelable接口必须重写该方法
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) { //实现Parcelable接口必须重写该方法
dest.writeString(name); //写出name
dest.writeInt(age); //写出age
}
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>(){ //这里提供了一个名为CREATOR的常量,创建了Parcelable.Creator接口的一个实现,并将泛型指定为Person
@Override
public Person[] newArray(int size) {
return new Person[size];
}
@Override
public Stu createFromParcel(Parcel source) { //读取写入的字段,需要注意的是写入和读取的顺序需要一致
Person person = new Person();
person.name = source.readString(); //读取name
person.age = source.readInt(); // 读取age
return s;
}
};
}
b.在主活动代码里:
Person person = new Person();
person.setName("Tom");
person.setAge(20);
Intent intent = new Intent(FirstAcitivity.this,SecondActivity.class);
intent.putExtra("person_data",person); //将实例传入
startActivity(intent);
c.在跳转的活动代码里:
Person person = (Person)getIntent.getParcelableExtra("person_data");
(3)Serializable和Parcelable两种方式的对比:
Serializable方式较为简单,但由于会把整个对象进行序列化,因此效率会比Parcelable的方式低一些,所以通常情况下更推荐使用Parcelable的方式实现Intent传递对象的功能。
三、定制自己的日志工具:
打印日志为我们调试程序提供了便利,但项目完成后,之前打印的日志在项目上线后仍然会打印日志,这会降低程序运行的效率,还有可能将机密数据泄漏出去。
理想情况是让日志在项目开发阶段可以打印,在项目发布上线后,不能打印日志。
实现方法如下:
(1)新建一个LogUtil类:
public class LogUtils {
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.v(tag, msg);
}
}
}
(2)使用自定义日志打印:
LogUtil.d("TAG","debug log"); //打印DEBUG级别的日志
LogUtil.w("TAG","warn log"); //打印WARN级别的日志
自定义日志打印只需要我们修改level变量的值,确定打印日志的级别,就可以自由控制日志的打印行为了。
四、创建定时任务:
Android 中的定时任务一般有两种实现方式,一种是使用Java API 里提供的 Timer 类,一种是使用 Android 的Alarm 机制。
Timer 类有一个缺点,它不适合用于那些需要长期在后台执行的定时任务。为了能让手机电池更加耐用,每种手机都有自己的休眠策略,Android 手机会在长时间不操作的情况下自动进入休眠状态,这就有可能导致 Timer 中的定时任务无法正确运行。
Alarm 机制具有唤醒 CPU 的功能 ,它可以保证在大多数情况下需要执行定时任务的时候CPU都能正常工作。唤醒CPU与唤醒屏幕完全不是一个概念。
- Alarm 机制借助AlarmManager类来实现,通过调用Context的getSystemService()方法来获取实例的,传入的参数是Context.ALARM_SERVICE:
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
- 接下来调用AlarmManager 的set()方法就可以设置一个定时任务了:
long triggerAtTime = SystemClock.elapsedRealtime()+10 * 1000;//时间10s
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent)
set()方法共有三个参数:
(1)第一个参数用于指定AlarmManager 的工作类型,有四个值可以选择:
ELAPSED_REALTIME:表示定时任务的触发时间从系统开机后开始计算,不会唤醒CPU;
ELAPSED_REALTIME_WAKEUP:表示定时任务的触发时间从系统开机后开始计算,会唤醒CPU;
RTC:表示定时任务的触发时间从1970年1月1日0点开始计算,不会唤醒CPU;
RTC_WAKEUP:表示定时任务的触发时间从1970年1月1日0点开始计算,会唤醒CPU。
SystemClock.elapsedRealTime()方法可以获取到系统开机至今所经历的的毫秒数, System.currentTimeMillis()方法可以获取到1970年1月1日0点至今所经历的的毫秒数。
(2)第二个参数指定任务触发的时间,以毫秒为单位。若第一个参数使用的是ELAPSED_REALTIME或ELAPSED_REALTIME_WAKEUP时,传入系统开机至今的时间再加上任务延迟执行的时间。若第一个参数使用的是RTC或RTC_WAKEUP,则传入1970年1月1日0点至今的时间再加上任务延迟执行的时间。
(3)第三个参数传入一个PendingIndent,一般会调用 getSevice()或getBroadCast()方法来获取一个能够执行服务或广播的PendingIntent。当任务被触发时,服务的onStartCommand()方法或广播接收器的onReceive()方法就可以得到执行。
- 实现一个长时间在后台定时运行的服务:
public class LongRunningService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
...
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 60*60*1000; //这是一小时的毫秒数
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent i = new Intent(this,LongRunningService.class);
PendingIntent pi = PendingIntent.getService(this,0,i,0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi);
return super.onStartCommand(intent, flags, startId);
}
}
启动定时服务时,调用如下代码:
Intent intent = new Intent(context, LongRunningService.class);
context.startService(intent);
注:从Android4.4系统开始,Alarm任务的触发时间将变得不准确,有可能会延迟一段时间后任务才得到执行,这是系统在耗电性方面的优化。
——参考《Android第一行代码》整理上述笔记