本篇文章主要介紹以幾下個(gè)知識(shí)點(diǎn):
- 獲取全局 Context
- 使用 Intent 傳遞對(duì)象
- 定制日志工具
- 創(chuàng)建定時(shí)任務(wù)
- 多窗口模式編程
13.1 全局獲取 Context 的技巧
??在某些情況下辜王,獲取 Context 并非是一件容易事李破,下面就來(lái)學(xué)習(xí)讓你在項(xiàng)目的任何地方都能輕松獲取到 Context 的一種技巧赠摇。
??Android 提供了一個(gè) Application 類西轩,每當(dāng)應(yīng)用程序啟動(dòng)時(shí)国觉,系統(tǒng)就會(huì)自動(dòng)將這個(gè)類進(jìn)行初始化。我們可以定制一個(gè)自己的 Application 類液兽,以便管理程序內(nèi)一些全局的狀態(tài)信息秦驯,比如全局的 Context。
??定制一個(gè)自己的 Application 并不復(fù)雜德澈,首先需要?jiǎng)?chuàng)建一個(gè) MyApplication 類繼承自 Application歇攻,如下:
public class MyApplication extends Application {
private static Context context;
@Override
public void onCreate() {
context = getApplicationContext();
}
public static Context getContext(){
return context;
}
}
??接下來(lái)在 AndroidManifest.xml 文件的<application>標(biāo)簽下進(jìn)行指定就可以了,如下:
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
. . .
</application>
??這樣就實(shí)現(xiàn)了一種全局獲取 Context 的機(jī)制梆造,之后在項(xiàng)目的任何地方想要獲取 Context缴守,只需調(diào)用 MyApplication.getContext() 就可以了,如彈吐司:
Toast.makeText(MyApplication.getContext(),"提示內(nèi)容",Toast.LENGTH_SHORT).show();
??任何一個(gè)項(xiàng)目都只能配置一個(gè) Application镇辉,當(dāng)引用第三方庫(kù)如 LitePal 時(shí)要配置 LitePalApplication 就會(huì)起沖突了屡穗,這種情況就要在自己的 Application 中去調(diào)用 LitePal 的初始化方法,如下:
public class MyApplication extends Application {
private static Context context;
@Override
public void onCreate() {
context = getApplicationContext();
// 調(diào)用 LitePal 的初始化方法
LitePal.initialize(context);
}
public static Context getContext(){
return context;
}
}
??使用上面的寫(xiě)法忽肛,把 Context 對(duì)象通過(guò)參數(shù)傳遞給 LitePal村砂,效果和在 AndroidManifest.xml 中配置 LitePalApplication 是一樣的。
??當(dāng)然屹逛,我個(gè)人是更習(xí)慣于通過(guò)獲取全局類實(shí)例的方法來(lái)定制自己的 Application 類础废,如下:
public class MyApplication extends Application {
private static MyApplication mInstance;
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
// 調(diào)用 LitePal 的初始化方法
LitePal.initialize(this);
}
/**
* Singleton main method. Provides the global static instance of the helper class.
* @return The MyApplication instance.
*/
public static synchronized MyApplication getInstance() {
return mInstance;
}
}
13.2 使用 Intent 傳遞對(duì)象
??使用 Intent 時(shí),可以在 Intent 中添加一些附加數(shù)據(jù)罕模,以達(dá)到傳值的效果评腺,如在第一個(gè)活動(dòng)中添加如下代碼:
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("string_data","hello");
intent.putExtra("int_data",100);
startActivity(intent);
??然后在第二個(gè)活動(dòng)中就可以獲得這些值了,如下:
getIntent().getStringExtra("string_data");
getIntent().getIntExtra("int_data",0);
??但上面的 putExtra() 方法中所支持的數(shù)據(jù)類型是有限的淑掌,若要傳遞一些自定義對(duì)象時(shí)就無(wú)從下手了歇僧,下面就來(lái)學(xué)習(xí)下用 Intent 來(lái)傳遞對(duì)象的技巧:Serializable 和 Parcelable。
13.2.1 Serializable 方式
??Serializable 是序列化的意思锋拖,表示將一個(gè)對(duì)象轉(zhuǎn)化成可儲(chǔ)存或可傳輸?shù)臓顟B(tài)诈悍。序列化的對(duì)象可在網(wǎng)絡(luò)上傳輸也可存儲(chǔ)到本地。將一個(gè)類序列化只要去實(shí)現(xiàn) Serializable 接口就可以了兽埃。
??比如一個(gè) Person 類侥钳,將它序列化可以這樣寫(xiě):
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;
}
}
??接下來(lái)在第一個(gè)活動(dòng)中的寫(xiě)法非常簡(jiǎn)單:
Person person = new Person();
person.setName("Tom");
person.setAge(18);
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("person_data",person);
startActivity(intent);
??然后在第二個(gè)活動(dòng)中獲取對(duì)象也非常簡(jiǎn)單:
Person person = (Person) getIntent().getSerializableExtra("person_data");
??這樣就實(shí)現(xiàn)了使用 Intent 傳遞對(duì)象了。
13.2.2 Parcelable 方式
??Parcelable方式的實(shí)現(xiàn)原理是將一個(gè)完整的對(duì)象進(jìn)行分解柄错,而分解后的每一部分都是 Intent 所支持的數(shù)據(jù)類型舷夺,這樣也就實(shí)現(xiàn)傳遞對(duì)象的功能了。
??Parcelable 的實(shí)現(xiàn)方式要稍微復(fù)雜一些售貌,修改 Person 中的代碼如下:
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() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
// 寫(xiě)出數(shù)據(jù):將 Person 類中的字段一一寫(xiě)出
dest.writeString(name);// 寫(xiě)出 name
dest.writeInt(age);// 寫(xiě)出 age
}
public static final Parcelable.Creator<Person>CREATOR = new Parcelable.Creator<Person>(){
@Override
public Person createFromParcel(Parcel source) {
// 讀取數(shù)據(jù):讀取的順序要和寫(xiě)出的順序完全相同
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];
}
};
}
??接下來(lái)在第一個(gè)活動(dòng)中的寫(xiě)法不變给猾,在第二個(gè)活動(dòng)中獲取對(duì)象稍加改動(dòng):
Person person = (Person)getIntent().getParcelableExtra("person_data");
??這樣就也實(shí)現(xiàn)使用 Intent 傳遞對(duì)象了。
??對(duì)比一下颂跨,Serializable 的方式較為簡(jiǎn)單敢伸,但由于把整個(gè)對(duì)象進(jìn)行序列化,效率會(huì)比 Parcelable 方式低恒削。一般推薦使用 Parcelable 的方式來(lái)實(shí)現(xiàn) Intent 傳遞對(duì)象的功能池颈。
13.3 定制自己的日志工具
??開(kāi)發(fā)一個(gè)項(xiàng)目時(shí),定制一個(gè)自己的日志工具能夠自由的控制日志的打印钓丰,當(dāng)程序處于開(kāi)發(fā)階段時(shí)讓日志打印出來(lái)躯砰,當(dāng)程序上線后把日志屏蔽。
??新建日志工具類 LogUtils 如下:
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 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);
}
}
}
??上述代碼提供了5個(gè)自定義的日志方法携丁,其內(nèi)部分別調(diào)用了 Android 自帶的打印日志方法琢歇,在項(xiàng)目里使用就像使用普通日志工具一樣,如打印一行 DEBUG 級(jí)別的日志可以這樣寫(xiě):
LogUtils.d("TAG","debug log");
??值得注意的是梦鉴,LogUtils 定義了一個(gè)靜態(tài)變量 level李茫,在開(kāi)發(fā)階段將 level 指定成 VERBOSE,當(dāng)項(xiàng)目正式上線時(shí)將 level 指定成 NOTHING尚揣,將所有日志屏蔽涌矢。
13.4 創(chuàng)建定時(shí)任務(wù)
??Android 中的定時(shí)任務(wù)一般有兩種實(shí)現(xiàn)方式,一種是使用 Java API 里提供的 Timer 類(不太適用于需要長(zhǎng)期在后臺(tái)運(yùn)行的定時(shí)任務(wù))快骗,一種是使用 Android 的 Alarm 機(jī)制(具有喚醒 CPU 功能娜庇,可以保證大多數(shù)情況下執(zhí)行定時(shí)任務(wù)時(shí) CPU 能正常工作)。
13.4.1 Alarm 機(jī)制
??Alarm 機(jī)制的用法不復(fù)雜方篮,主要是借助 AlarmManager 類來(lái)實(shí)現(xiàn)的名秀。比如想要設(shè)定一個(gè)任務(wù)在 10 秒鐘后執(zhí)行,可寫(xiě)成:
// 獲取 AlarmManager 的實(shí)例
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
// 設(shè)置觸發(fā)時(shí)間
// SystemClock.elapsedRealtime() 獲取系統(tǒng)開(kāi)機(jī)至今所經(jīng)歷時(shí)間的毫秒數(shù)
// SystemClock.currentTimeMillis() 獲取1970年1月1日0點(diǎn)至今所經(jīng)歷時(shí)間的毫秒數(shù)
long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
// 3個(gè)參數(shù):指定 AlarmManager 的工作類型藕溅、定時(shí)任務(wù)的觸發(fā)時(shí)間匕得、PendingIntent
// 其中AlarmManager 的工作類型有四種:
// ELAPSED_REALTIME 定時(shí)任務(wù)的觸發(fā)時(shí)間從系統(tǒng)開(kāi)機(jī)開(kāi)始時(shí)算起,不會(huì)喚醒 CPU
// ELAPSED_REALTIME_WAKEUP 系統(tǒng)開(kāi)機(jī)開(kāi)始時(shí)算起,會(huì)喚醒 CPU
// RTC 從1970年1月1日0點(diǎn)開(kāi)始算起汁掠,不會(huì)喚醒 CPU
// RTC_WAKEUP 從1970年1月1日0點(diǎn)開(kāi)始算起略吨,會(huì)喚醒 CPU
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pandingIntent);
??舉個(gè)例子,實(shí)現(xiàn)一個(gè)長(zhǎng)時(shí)間在后臺(tái)定時(shí)運(yùn)行的服務(wù)考阱,首先新建一個(gè)普通的服務(wù) LongRunningService翠忠,將觸發(fā)定時(shí)任務(wù)的代碼寫(xiě)到 onStartCommand() 方法中,如下:
public class LongRunningService extends Service {
public LongRunningService() {
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 在這里執(zhí)行具體的邏輯操作
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 60 * 60 * 1000;//1小時(shí)的毫秒數(shù)
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);
}
}
??然后,要啟動(dòng)定時(shí)服務(wù)時(shí)調(diào)用如下代碼即可:
Intent intent = new Intent(context,LongRunningService.class);
context.startService(intent);
??值得注意的是,從 Android 4.4開(kāi)始匈睁,由于系統(tǒng)在耗電方面的優(yōu)化柏副,Alarm 任務(wù)的觸發(fā)時(shí)間變得不準(zhǔn)確,可能會(huì)延遲一段時(shí)間后再執(zhí)行。當(dāng)然,使用 AlarmManager 的 setExact() 方法來(lái)替代 set() 方法,基本上可以保證任務(wù)準(zhǔn)時(shí)執(zhí)行河质。
13.4.2 Doze 模式
??在 Android 6.0中,谷歌加入了一個(gè)全新的 Doze 模式申鱼,可以極大幅度地延長(zhǎng)電池的使用壽命愤诱。下面就來(lái)了解下這個(gè)模式,掌握一些編程時(shí)注意事項(xiàng)捐友。
??在 6.0 及以上系統(tǒng)的設(shè)備上淫半,若未插接電源,處于靜止?fàn)顟B(tài)(7.0中刪除了這一條件)匣砖,且屏幕關(guān)閉了一段時(shí)間之后科吭,就會(huì)進(jìn)入到 Doze 模式。在 Doze 模式下猴鲫,系統(tǒng)會(huì)對(duì) CPU对人、網(wǎng)絡(luò)、Alarm 等活動(dòng)進(jìn)行限制拂共,從而延長(zhǎng)電池的使用壽命牺弄。
??當(dāng)然,系統(tǒng)不會(huì)一直處于 Doze 模式宜狐,而是會(huì)間歇性的退出一小段時(shí)間势告,在這段時(shí)間應(yīng)用可以去完成它們的同步操作、Alarm 任務(wù)等抚恒,其工作過(guò)程如下:
??Doze 模式下受限的功能有:
?(1)網(wǎng)絡(luò)訪問(wèn)被禁止
?(2)系統(tǒng)忽略喚醒CPU或屏幕操作
?(3)系統(tǒng)不再執(zhí)行WIFI掃描
?(4)系統(tǒng)不再執(zhí)行同步任務(wù)
?(5)Alarm 任務(wù)將會(huì)在下次退出 Doze 模式時(shí)執(zhí)行
??特殊需求咱台,要 Alarm 任務(wù)在 Doze 模式下也必須正常執(zhí)行,則可以調(diào)用 AlarmManager 的 setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle() 方法俭驮。
13.5 多窗口模式編程
??Android 7.0中引入了一個(gè)非常有特色的功能——多窗口模式回溺,允許在同一個(gè)屏幕中同時(shí)打開(kāi)兩個(gè)應(yīng)用程序。
13.5.1 進(jìn)入多窗口模式
??手機(jī)的導(dǎo)航欄上有3個(gè)按鈕:左邊 Back 按鈕、中間 Home 按鈕遗遵、右邊 OverView 按鈕萍恕,如下所示:
??OverView 按鈕的作用是打開(kāi)一個(gè)最近訪問(wèn)過(guò)的活動(dòng)或任務(wù)的列表界面,進(jìn)入多窗口模式需要用到 OverView 按鈕瓮恭,并且有兩種方式:
在 OverView 列表界面長(zhǎng)按任意一個(gè)活動(dòng)的標(biāo)題雄坪,將該活動(dòng)拖到屏幕突出顯示的區(qū)域,則可以進(jìn)入多窗口模式屯蹦。
打開(kāi)任意一個(gè)程序,長(zhǎng)按 OverView 按鈕绳姨,也可以進(jìn)入多窗口模式登澜。
??多窗口模式效果如下:
??可以看出多窗口模式下,應(yīng)用界面縮小很多飘庄,編寫(xiě)程序時(shí)要多考慮使用 match_parent 屬性脑蠕、RecyclerView、ScrollView 等控件跪削,適配各種不同尺寸的屏幕谴仙。
13.5.2 多窗口模式下的生命周期
??多窗口模式并不會(huì)改變活動(dòng)原有的生命周期,只是會(huì)將用戶最近交互過(guò)的那個(gè)活動(dòng)設(shè)置為運(yùn)行狀態(tài)碾盐,而將多窗口模式下另外一個(gè)可見(jiàn)的活動(dòng)設(shè)置為暫停狀態(tài)晃跺。若這時(shí)用戶又去和暫停的活動(dòng)進(jìn)行交互,那么該活動(dòng)就變成運(yùn)行狀態(tài)毫玖,之前處于運(yùn)行狀態(tài)的活動(dòng)變成暫停狀態(tài)掀虎。
??進(jìn)入多窗口模式時(shí)活動(dòng)會(huì)被重新創(chuàng)建,若要改變這一默認(rèn)行為付枫,可以在 AndroidManifest.xml 中對(duì)活動(dòng)添加如下配置:
android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
??添加這行配置后烹玉,不管是進(jìn)入多窗口模式還是橫豎屏切換,活動(dòng)都不會(huì)被重新創(chuàng)建阐滩,而是會(huì)將屏幕發(fā)生變化的事件通知到 Activity 的 onConfigurationChanged() 方法中二打。因此,若要在屏幕發(fā)生變化時(shí)進(jìn)行相應(yīng)的邏輯處理掂榔,那么在活動(dòng)中重寫(xiě) onConfigurationChanged() 方法即可继效。
13.5.3 禁用多窗口模式
??禁用多窗口模式的方法很簡(jiǎn)單,只需在 AndroidManifest.xml 的<application>或<activity>標(biāo)簽中加入如下屬性即可:
android:resizeableActivity=["true"|"false"]
??其中衅疙,true 表示支持多窗口模式莲趣,false 表示不支持,若不配置這屬性默認(rèn)是 true饱溢。
??雖說(shuō) android:resizeableActivity 這個(gè)屬性的用法簡(jiǎn)單喧伞,但這個(gè)屬性只適用于 targetSdkVersion 24 或更高的時(shí)候,若低于24則無(wú)效,可能會(huì)被告知此應(yīng)用在多窗口模式下可能無(wú)法正常工作潘鲫,并進(jìn)入多窗口模式翁逞。
??Android 規(guī)定,若項(xiàng)目指定的 targetSdkVersion 低于24溉仑,并且活動(dòng)是不允許橫豎屏切換時(shí)是不支持多窗口模式的挖函。因此針對(duì)上面的情況,就需要在 AndroidManifest.xml 的<activity>標(biāo)簽中配置如下屬性:
android:screenOrientation=["portrait"|"landscape"]
??其中浊竟,portrait 表示只支持豎屏怨喘,landscape 表示只支持橫屏。
??本篇文章就介紹到這振定。