1. 全局获取 Context 技巧

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

public class MyApplication extends Application {
	private static Context context;
	@Override
	public void onCreate() {
		//获取一个程序级别的Context
		context = getApplicationContext();
	}
	public static Context getContext() {
		return context;
	}
}

接下来我们需要告知系统,当程序启动的时候应该初始化 MyApplication 类,而不是默认的
Application 类。在 AndroidManifest.xml 文件的 标签下进行指定就可以了。

<application
	android:name="com.example.networktest.MyApplication"
	...>

2. 使用 Intent 传递对象

2.1 Serializable 方式

让类实现序列号接口

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;
    }
}

传输及获取

// 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);

// SecondActivity
Person person = (Person) getIntent().getSerializableExtra("person_data");

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 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];
        }
    };
}
  • 首先我们让 Person 类去实现了 Parcelable 接口
  • 重写 describeContents() 和 writeToParcel() 这两个方法
  • describeContents() 方法直接返回 0 就可以了
  • writeToParcel() 方法中我们需要调用 Parcel 的 writeXxx() 方法将 Person 类中的字段一一写出
  • 我们还必须在 Person 类中提供一个名为 CREATOR 的常量,这里创建了 Parcelable.Creator 接口的一个实现,并将泛型指定为 Person。
  • 接着需要重写 createFromParcel() 和 newArray() 这两个方法,在 createFromParcel() 方法中我们要去读取刚才写出的 name 和 age字段,并创建一个 Person 对象进行返回,其中 name 和 age 都是调用 Parcel 的 readXxx()方法读取到的,注意这里读取的顺序一定要和刚才写出的顺序完全相同。
  • 而 newArray() 方法中的实现就简单多了,只需要 new 出一个 Person 数组,并使用方法中传入的 size 作为数组大小就可以了。

传输及获取

// 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);

// SecondActivity
Person person = (Person) getIntent().getParcelableExtra("person_data");

对比一下,Serializable 的方式较为简单,但由于会把整个对象进行序列化,因此效率方面会比 Parcelable 方式低一些,所以在通常情况下还是更加推荐使用 Parcelable 的方式来实现 Intent 传递对象的功能。

3. 定时任务

3.1 Timer 和 Alarm

Android 中的定时任务一般有两种实现方式,一种是使用 Java API 里提供的 Timer 类,一种是使用 Android 的 Alarm 机制。两种方式在大部分情况下都能实现类似的效果,但是 Timer 类有一个缺点,它并不适合用于那些需要长期在后台执行的定时任务。一般情况下,为了能让手机电池更加耐用,每种手机都有自己的休眠策略,Android 手机会在长时间不操作的情况下自动进入休眠状态,这就有可能导致 Timer 中的定时任务无法正确运行。而 Alarm 则具有唤醒 CPU 的功能 ,它可以保证在大多数情况下需要执行定时任务的时候 CPU都能正常工作。(唤醒 CPU 和唤醒屏幕并不是一个概念)

3.2 Alarm

Alarm 主要借助 AlarmManager 类来实现。

获取一个 AlarmManager 实例

AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

调用 AlarmManager 的 set()方法设置一个定时任务了,比如说想要设定一个任务在 10 秒钟后执行,就可以写成:

long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent);

// 或者写成
long triggerAtTime = System.currentTimeMillis() + 10 * 1000;
manager.set(AlarmManager.RTC_WAKEUP, triggerAtTime, pendingIntent);
  • 第一个参数是一个整型参数,用于指定 AlarmManager 的工作类型
    • ELAPSED_REALTIME 表示让定时任务的触发时间从系统开机开始算起,但不会唤醒 CPU。
    • ELAPSED_REALTIME_WAKEUP 同样表示让定时任务的触发时间从系统开机开始算起,但会唤醒 CPU。
    • RTC 表示让定时任务的触发时间从 1970 年 1月 1 日 0 点开始算起,但不会唤醒 CPU。
    • RTC_WAKEUP 同样表示让定时任务的触发时间从1970 年 1 月 1 日 0 点开始算起,但会唤醒 CPU。
  • 第二个参数,定时任务触发的时间,以毫秒为单位。
  • 三个参数是一个 PendingIntent.这里我们一般会调用 etBroadcast() 方法来获取一个能够执行广播的 PendingIntent。这样当定时任务被触发的时候,广播接收器的 onReceive() 方法就可以得到执行。
  • 由于安卓官方为了 CUP 节能考虑,把相近时间唤醒 CPU 的服务都放到一期来启动,需要精确时间唤醒的可以把 set() 换成 setExact()
  • 使用 SystemClock.elapsedRealtime() 方法可以获取到系统开机至今所经历时间的毫秒数,
  • 使用 System.currentTimeMillis() 方法可以获取到 1970 年 1 月 1 日 0 点至今所经历时间的毫秒数。

实现定时启动的服务

创建服务,并定时发出广播

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "当前时间是"+ new Date().toString());
        }
    }).start();

    AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
    int HowLong = 60 * 60 * 1000;                                                //设定定时时间
    long triggerAtTime = SystemClock.elapsedRealtime() + HowLong;               //加上系统开机时间
    Intent intent1 = new Intent(this, AlarmReceiver.class);                       //创建Intent指向广播接收器
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent1, 0);//调用 getBroadcast()方法来获取一个能够执行广播的 PendingIntent
    alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent);//设置定时唤醒,并发出广播
    return super.onStartCommand(intent, flags, startId);
}

在广播中执行开始下一次定时任务

public class AlarmReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Intent AlarmIntent;
        AlarmIntent = new Intent(context,LongRunningService.class);
        context.startService(AlarmIntent);
    }
}

在需要的地方首次启动服务

Intent intent = new Intent(this,LongRunningService.class);
startService(intent);