Android中的高級(jí)技巧

獲取全局Context的技巧

我們首先看一下Context類的結(jié)構(gòu)

1

不難看出Context一共有三種類型,分別是Application、Activity和Service。這三個(gè)類雖然分別各種承擔(dān)著不同的作用,但它們都屬于Context的一種,而它們具體Context的功能則是由ContextImpl類去實(shí)現(xiàn)的.由于Context的具體能力是由ContextImpl類去實(shí)現(xiàn)的,因此在絕大多數(shù)場景下,Activity、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊,比如啟動(dòng)Activity,還有彈出Dialog。出于安全原因的考慮,Android是不允許Activity或Dialog憑空出現(xiàn)的,一個(gè)Activity的啟動(dòng)必須要建立在另一個(gè)Activity的基礎(chǔ)之上,也就是以此形成的返回棧。而Dialog則必須在一個(gè)Activity上面彈出(除非是System Alert類型的Dialog),因此在這種場景下,我們只能使用Activity類型的Context,否則將會(huì)出錯(cuò)。

我們可能有的時(shí)候會(huì)為獲取不到Context而發(fā)愁,因?yàn)楹芏嗟胤蕉紩?huì)用到Context,比如說啟動(dòng)服務(wù),啟動(dòng)活動(dòng),發(fā)送廣播,操作數(shù)據(jù)庫,使用通知,彈出Toast等等.當(dāng)我們操作在activity中使因?yàn)榛顒?dòng)本身就是一個(gè)Context類對(duì)象,所以我們不會(huì)發(fā)愁,但是當(dāng)應(yīng)用程序的架構(gòu)逐漸開始復(fù)雜起來的時(shí)候,很多的邏輯代碼都將脫離Activity類,而你又恰恰需要使用Context,也許這個(gè)時(shí)候你會(huì)感到有點(diǎn)傷腦筋了.

我們來學(xué)習(xí)一種技巧,讓我們在項(xiàng)目的任何地方都能輕松獲取到Context

Android提供了一個(gè)Application類,每當(dāng)程序啟動(dòng)的時(shí)候,系統(tǒng)會(huì)自動(dòng)將這個(gè)類進(jìn)行初始化.我們可以定制一個(gè)自己的Application類以便于管理程序內(nèi)一些全局的狀態(tài)信息,比如全局Context.方法為:

1.創(chuàng)建一個(gè)MyApplication類來繼承Application

2.重寫父類的onCreate()方法,并通過調(diào)用getApplicationContext()方法得到了一個(gè)應(yīng)用程序級(jí)別的Context

3.提供一個(gè)靜態(tài)方法public static Context getContext()來返回剛才獲取到的Context

2

4.告知系統(tǒng),當(dāng)程序啟動(dòng)的時(shí)候應(yīng)該初始化MyApplication類,而不是默認(rèn)的Application類.方法是-----在AndroidManifest.xml文件的<application>標(biāo)簽下進(jìn)行指定就可以了.注意在指定MyApplication的時(shí)候一定要加上完整的包名,不然系統(tǒng)無法找到這個(gè)類.

3

5.想在項(xiàng)目的哪個(gè)地方使用Context,只需要調(diào)用MyApplication.getContext()就可以了.

注意:如果像LitePal這樣的開源框架,因?yàn)長itePal也需要在AndroidManifest.xml中<application>聲明android:name 因?yàn)橹挥羞@樣聲明之后LitePal才會(huì)在內(nèi)部自動(dòng)獲取到Context,這樣便和我們自己聲明的MyApplication產(chǎn)生沖突了,因?yàn)槿魏我粋€(gè)項(xiàng)目只能有一個(gè)Application.這種情況的解決方案是,在我們自己的MyApplication中調(diào)用LitePal的初始化方法就可以了.

public void onCreate() {

super.onCreate();

? ? mContext = getApplicationContext();

? ? //LitePal的處理辦法

//LitePal.initialize(mContext);

}

4

相當(dāng)于我們把全局的Context對(duì)象通過參數(shù)傳遞給了LitePal,效果和在AndroidManifest.xml中配置LitePalApplication是一摸一樣的.

使用Intent傳遞對(duì)象

Intent可以用來啟動(dòng)活動(dòng),發(fā)送廣播,啟動(dòng)服務(wù)等,我們還可以在進(jìn)行上述操作的時(shí)候進(jìn)行傳遞數(shù)據(jù),進(jìn)行通信.

我們學(xué)習(xí)一下使用Intent來傳遞對(duì)象的技巧---Serializable/Parcelable

Serializable方式

Serializable是序列化的意思,表示將一個(gè)對(duì)象轉(zhuǎn)換成可存儲(chǔ)或可傳輸?shù)臓顟B(tài).序列化后的對(duì)象可以在網(wǎng)絡(luò)上進(jìn)行傳輸,也可以存儲(chǔ)到本地.序列化的方法很簡單,只需要讓一個(gè)類去實(shí)現(xiàn)Serializable這個(gè)接口就可以了.

5

Parcelable方式

不同于將對(duì)象進(jìn)行序列化,Parcelable方式的實(shí)現(xiàn)原理是將一個(gè)完整的對(duì)象進(jìn)行分解,而分解后的每一部分都是Intent所支持的數(shù)據(jù)類型,這樣也就實(shí)現(xiàn)了傳遞對(duì)象的功能了.

6

下面那兩個(gè)方法在as中很簡單,選中Person然后alt+enter便出來了

7
8

定制自己的日志工具

主要為了解決當(dāng)我們編寫一個(gè)龐大的項(xiàng)目的時(shí)候我們可能期間為了方便調(diào)試,在代碼的很多地方都打印了大量的日志,最近項(xiàng)目基本完成了,但是有一個(gè)很頭疼的問題,之前用于調(diào)試的那些日志,在正式上線之后仍然會(huì)照常打印,這樣不僅會(huì)降低程序的運(yùn)行效率,還可能將一些機(jī)密的數(shù)據(jù)泄露出去.最理想的情況是能夠自由控制日志的打印,當(dāng)程序處于開發(fā)階段時(shí)就讓日志打印出來,當(dāng)程序上線了之后就把日志屏蔽掉.

9
10

只需要修改level變量的值,就可以自由地控制日志的打印行為了.比如讓level等于VERBOSE就可以將所有的日志都打印出來,讓level等于WARN就可以只打印警告以上級(jí)別的日志,讓level等于NOTHING就可以把所有日志都屏蔽掉.

解決剛開始我們提到的那個(gè)問題我們只需要在開發(fā)階段將level指定成VERBOSE,當(dāng)項(xiàng)目正式上線的時(shí)候?qū)evel指定成NOTHING就可以了

調(diào)試Android程序

設(shè)置斷點(diǎn),啟動(dòng)Debug調(diào)試程序

1.添加斷點(diǎn)--------在相應(yīng)的代碼行的左邊點(diǎn)擊一下便可以了

若想取消這個(gè)斷點(diǎn)-------對(duì)著它再點(diǎn)擊一下就可以了

2.在Android studio頂部工具欄中的Debug按鈕,就會(huì)使用調(diào)試模式來啟動(dòng)程序

3.接下來每按一次F8鍵,代碼便會(huì)向下執(zhí)行一行,并且通過Variables視圖還可以看到內(nèi)存中的數(shù)據(jù)

4.調(diào)試完成之后點(diǎn)擊Debug窗口中的Stop按鈕來結(jié)束調(diào)試即可

缺點(diǎn):這種調(diào)試模式下,程序的運(yùn)行效率大大降低,如果添加的斷點(diǎn)在比較靠后的位置,需要執(zhí)行很多的操作才能運(yùn)行到這個(gè)斷點(diǎn),那么前面這些操作便會(huì)有卡頓的效果

Attach debugger to Android process 模式(推介使用這種模式)

正常方式啟動(dòng)程序,先將待調(diào)試的程序部分的準(zhǔn)備工作做好(比如說我們?nèi)绻{(diào)試輸入賬號(hào)密碼那么需要提前輸入賬號(hào)密碼)然后點(diǎn)擊Android Studio頂部的工具欄中的Attach debugger to Android process按鈕,會(huì)彈出一個(gè)進(jìn)程選擇提示框,選中這個(gè)進(jìn)程,然后點(diǎn)擊OK按鈕,就可以讓這個(gè)進(jìn)程進(jìn)入到調(diào)試模式了.

接下來進(jìn)入調(diào)試模式之后和上一種調(diào)試方法便一樣了,Android Studio同樣會(huì)自動(dòng)打開Debug窗口,之后流程都是相同的了,第二種調(diào)試方式會(huì)比第一種更加靈活,也更常用.(第二種調(diào)試存在問題待解決......)

創(chuàng)建定時(shí)任務(wù)

JAVA中的Timer類

Timer類有一個(gè)短板,它并不適合用于那些需要長期在后臺(tái)運(yùn)行的定時(shí)任務(wù).我們知道為了讓電池更加耐用,每種手機(jī)都會(huì)有自己的休眠策略,Android手機(jī)就會(huì)在長時(shí)間不操作的情況下自動(dòng)讓CPU進(jìn)入睡眠狀態(tài),這就有可能導(dǎo)致Timer的定時(shí)任務(wù)無法正常運(yùn)行.

Alarm機(jī)制

Alarm具有喚醒CPU的功能,它可以保證大多數(shù)情況下需要執(zhí)行定時(shí)任務(wù)的時(shí)候CPU都能正常工作.需要注意的是喚醒CPU和喚醒屏幕是兩種不同的概念不要混淆.

Alarm機(jī)制的用法:

1.借用AlarmManager類來獲取一個(gè)AlarmManager的實(shí)例,這個(gè)類和NotificationManager有點(diǎn)類似,都是通過Context的getSystemService()方法來獲取實(shí)例的,只是這里需要傳入的參數(shù)是Context.ALARM_SERVICE.獲取一個(gè)AlarmManager的實(shí)例可以寫成

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

2.接著調(diào)用AlarmManager的set()方法就可以設(shè)置一個(gè)定時(shí)任務(wù)了.

long triggerAtTime= SystemClock.elapsedRealtime()+10*1000;

manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);

第一個(gè)方法中解釋:

使用SystemClock.elapsedRealtime()方法可以獲取到系統(tǒng)開機(jī)至今所經(jīng)歷時(shí)間的毫秒數(shù)

使用System.currentTimeMillis()方法可以獲取到1970年1月1日0點(diǎn)至今所經(jīng)歷的時(shí)間的毫秒數(shù)

第二個(gè)方法的各個(gè)參數(shù)解釋

第一個(gè)參數(shù)是整形參數(shù)用于指定AlarmManager的工作類型,有四種值可選.

AlarmManager.ELAPSED_REALTIME:讓定時(shí)任務(wù)的觸發(fā)時(shí)間從系統(tǒng)開機(jī)開始算起,但不會(huì)喚醒CPU

AlarmManager.ELAPSED_REALTIME_WAKEUP:讓定時(shí)任務(wù)的觸發(fā)時(shí)間從系統(tǒng)開機(jī)開始算起,會(huì)喚醒CPU

AlarmManager.RTC:讓定時(shí)任務(wù)的觸發(fā)時(shí)間從1970年1月1日開始算起,但是不會(huì)喚醒CPU

AlarmManager.RTC_WAKEUP:讓定時(shí)任務(wù)的觸發(fā)時(shí)間從1970年1月1日開始算起,會(huì)喚醒CPU

第二個(gè)參數(shù)是定時(shí)任務(wù)的觸發(fā)時(shí)間,以毫秒為單位.

第三個(gè)參數(shù)是一個(gè)PendingIntent.一般我們用getService()方法或者getBroadcast()方法來獲取一個(gè)能夠執(zhí)行廣播或服務(wù)的PendingIntent.

需要注意的是:從android4.4系統(tǒng)開始,Alarm任務(wù)的觸發(fā)時(shí)間變得不是很準(zhǔn)確,可能會(huì)延遲一段時(shí)間后任務(wù)才能得到執(zhí)行,這并不是bug而是系統(tǒng)在耗電性方面進(jìn)行的優(yōu)化.系統(tǒng)會(huì)自動(dòng)檢測目前有多少alarm任務(wù)存在,然后將觸發(fā)時(shí)間相近的幾個(gè)任務(wù)放在一起執(zhí)行,這就可以大幅度減少CPU被喚醒的次數(shù),從而有效延長電池的使用時(shí)間.

如果你要求alarm任務(wù)的執(zhí)行時(shí)間準(zhǔn)確無誤,android仍然提供了解決方案,使用AlarmManager的setExact()方法來代替set()方法,基本上就能保證任務(wù)能夠準(zhǔn)時(shí)執(zhí)行了.

Doze模式

背景:雖然Android的每個(gè)系統(tǒng)版本都在手機(jī)電量方面努力進(jìn)行優(yōu)化,不過一直沒有能夠解決后臺(tái)服務(wù)泛濫,手機(jī)電量消耗過快的問題.在Android6.0系統(tǒng)中谷歌加入了一個(gè)新的Doze模式,從而可以極大幅度的延長手機(jī)電池的使用壽命.

Doze模式:當(dāng)用戶的設(shè)備是Android6.0或以上系統(tǒng)時(shí),如果該設(shè)備未插接電源,處于靜止?fàn)顟B(tài)(Android7.0中刪除了處于靜止?fàn)顟B(tài)這一條件),且屏幕關(guān)閉了一段時(shí)間之后,就會(huì)進(jìn)入到Doze模式.在Doze模式下,系統(tǒng)會(huì)對(duì)CPU,網(wǎng)絡(luò),Alarm等活動(dòng)進(jìn)行限制,從而延長了電池的使用壽命.

系統(tǒng)并不會(huì)一直處于Doze模式,而是會(huì)間歇性退出Doze模式一小段時(shí)間,在這段時(shí)間中,應(yīng)用就可以去完成它們的同步操作,Alarm任務(wù)等.

11
12

如果你對(duì)Alarm任務(wù)即使在Doze模式下也必須正常運(yùn)行,Android還是提供了解決方案的,調(diào)用AlarmManager的setAndAllowWhileIdle()或setExactAndAllowWhileIdle()方法讓定時(shí)任務(wù)即使在Doze模式下也能正常執(zhí)行了.這兩個(gè)方法之間的區(qū)別和set(),setExact()方法之間的區(qū)別是一樣的.

多窗口模式編程

Android7.0系統(tǒng)中引入了多窗口模式,它允許我們在同一個(gè)屏幕中同時(shí)打開兩個(gè)應(yīng)用程序.

在多窗口模式下,整個(gè)應(yīng)用的界面會(huì)縮小很多,那么編寫程序時(shí)就應(yīng)該多考慮使用match_parent屬性,RecyclerView,ListView,ScrollView等控件,來讓應(yīng)用的界面能夠更好地適配各種不同尺寸的屏幕,盡量不要出現(xiàn)屏幕尺寸變化過大使界面無法正常顯示的情況.

多窗口模式下的生命周期

多窗口模式并不會(huì)改變活動(dòng)原有的生命周期,只是會(huì)將最近交互過的那個(gè)活動(dòng)(即剛開啟窗口的那個(gè)活動(dòng))設(shè)置為運(yùn)行狀態(tài),而將多窗口模式下另一個(gè)可見的活動(dòng)設(shè)置為暫停狀態(tài),這時(shí)用戶又去和暫停的活動(dòng)進(jìn)行交互,那么該活動(dòng)就編程運(yùn)行狀態(tài),之前處于運(yùn)行狀態(tài)的活動(dòng)編程暫停狀態(tài).

打開一個(gè)MaterialTest項(xiàng)目首先onCreate ?onStart ?onResum方法啟動(dòng),然后進(jìn)入多窗口模式后onPause onStop onDestory?onCreate ?onStart ?onResum onPause方法啟動(dòng)

進(jìn)入多窗口模式后活動(dòng)的大小發(fā)生了比較大的變化,此時(shí)默認(rèn)是會(huì)重新創(chuàng)建活動(dòng)的.除此之外,像橫豎屏切換也是會(huì)重新創(chuàng)建活動(dòng)的.進(jìn)入多窗口模式后,MaterialTest變成暫停狀態(tài).在Overview界面選中LBSTest程序,LBSTest的onCreate onStart onResum方法依次得到執(zhí)行說明LBSTest變成了運(yùn)行狀態(tài).然后我們再操作一下MaterialTest程序,發(fā)現(xiàn)LBSTest的onPause方法執(zhí)行,MaterialTest的onResum方法得到了執(zhí)行,說明LBSTest變成了暫停狀態(tài),MaterialTest變成了運(yùn)行狀態(tài).

了解了多窗口模式的生命周期的作用:在多窗口模式下,用戶仍然可以看到處于暫停狀態(tài)下的應(yīng)用,那么像視頻播放之類的應(yīng)用此時(shí)就應(yīng)該能播放視頻才對(duì),我們最好不要在活動(dòng)的onPause方法中處理視頻播放器的暫停邏輯,而是應(yīng)該在onStop方法中去處理,并且在onStart方法中恢復(fù)視頻的播放.

針對(duì)進(jìn)入多窗口模式時(shí)程序會(huì)被重新創(chuàng)建,如果我們想改變這一行為,我們可以在AndroidManifest.xml文件中對(duì)活動(dòng)(在活動(dòng)標(biāo)簽中配置)進(jìn)行配置

android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"

然后不管進(jìn)入多窗口,還是橫豎屏切換,活動(dòng)都不會(huì)被重新創(chuàng)建,而是將屏幕發(fā)生變化的事件通知到Activity的onConfigurationChanged()方法當(dāng)中.如果想在屏幕發(fā)生變化的時(shí)候進(jìn)行相應(yīng)的邏輯處理,那么在活動(dòng)中重寫onConfigurationChanged()方法即可.

禁用多窗口模式

在androidManifest.xml的<application>或者<activity>標(biāo)簽中加入

android:resizeableActivity=["true"|"false"]

其中true表示支持多窗口模式false表示不支持.(默認(rèn)值是true即支持多窗口模式)

但存在一個(gè)問題也就是低版本這個(gè)屬性對(duì)低版本不支持,這個(gè)屬性只有當(dāng)項(xiàng)目的targetSdkVersion指定成24或者更高的時(shí)候才會(huì)有用,否則這個(gè)屬性是無效的.針對(duì)這種情況Android提供了一種解決方案:如果項(xiàng)目指定的targetSDKVersion低于24,并且活動(dòng)是不允許橫豎屏切換的,那么應(yīng)用也就不支持多窗口模式.

想讓應(yīng)用不允許橫豎屏切換,需要在AndroidManifest.xml文件中的<activity>標(biāo)簽中加入如下配置:

android:screenOrientation=["portrait"|"landscape"]

portrait表示活動(dòng)只支持豎屏,landscape表示活動(dòng)只支持橫屏

Lambda表達(dá)式

Lanmbda表達(dá)式本質(zhì)是一種匿名方法,它沒有方法名,也沒有訪問修飾符和返回值類型,使用它來編寫代碼將會(huì)變得簡潔,也更加易讀.

首先在app/build.gradle中添加如下配置:

android{

defaultConfig{

android.compileOptions.sourceCompatibility 1.8

android.compileOptions.targetCompatibility 1.8}}

注意:第一行代碼中寫的那個(gè)已經(jīng)過時(shí)了!!!!

但凡是這種只有一個(gè)待實(shí)現(xiàn)方法的接口都可以使用Lambda表達(dá)式,

13
14

另外Java還可以根據(jù)上下文自動(dòng)推斷出Lambda表達(dá)式中的參數(shù)類型.

15

當(dāng)接口的待實(shí)現(xiàn)方法有且只有一個(gè)參數(shù)的時(shí)候,我們還可以進(jìn)行另一步簡化,將參數(shù)外面的括號(hào)去掉.

16

總結(jié):

17
18
19
20
21
22
23
24
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容