一 Activity
1 Activity 生命周期
1.1 Activity 的四種狀態(tài)
running 當(dāng)前Activity正在運(yùn)行,獲取焦點(diǎn)
paused 當(dāng)前Activity處于暫停狀態(tài),可見,沒有焦點(diǎn)
stopped 當(dāng)前Activity處于暫停狀態(tài),完全不可見,內(nèi)存里的成員變量和狀態(tài)信息仍在。
killed 當(dāng)前Activity被銷毀后的狀態(tài),成員變量和狀態(tài)信息被一并回收。
1.2 Activity的生命周期
Activity啟動(dòng) →onCreate()→onStart()→onResume();
點(diǎn)擊home鍵返回桌面→onPause()→onStop();
再次回到原Activity→ onRestart()→onStart()→onResume();
按返回鍵退出當(dāng)前Activity→onPause()→onStop()→onDestroy();
2 Android任務(wù)棧
優(yōu)先級(jí):前臺(tái)>可見>服務(wù)>后臺(tái)>空
前臺(tái):正在與用戶進(jìn)行交互的Activity所在的進(jìn)程
可見:Activity可見但沒有在前臺(tái)所在的進(jìn)程
服務(wù):Activity在后臺(tái)開啟了服務(wù)所在的進(jìn)程
后臺(tái):Activity完全處于后臺(tái)所在的進(jìn)程
空:沒有任何Activity存在的進(jìn)程
3. Activity的啟動(dòng)模式
3.1 為什么需要啟動(dòng)模式?
? ? ? ?每次啟動(dòng)一個(gè)Activity都會(huì)把對(duì)應(yīng)的要啟動(dòng)的Activity的實(shí)例放入任務(wù)棧中,加入這個(gè)Activity被頻繁啟動(dòng),會(huì)產(chǎn)生很多的這個(gè)Activity的實(shí)例,為了杜絕這種內(nèi)存浪費(fèi)的行為,Activity的啟動(dòng)模式被創(chuàng)造出來。
3.2 Activity的啟動(dòng)模式
- 系統(tǒng)模式模式:standard
? ? ? ?標(biāo)準(zhǔn)模式,也是系統(tǒng)的默認(rèn)模式,啟動(dòng)一個(gè)activity就創(chuàng)建一個(gè)activity實(shí)例,不管這個(gè)實(shí)例是否存在,誰啟動(dòng)了這個(gè)Activity,那么這個(gè)Activity就運(yùn)行在啟動(dòng)它的那個(gè)Activity的任務(wù)棧中。 - 棧頂復(fù)用模式:singleTop
? ? ? ?在這種模式下,如果新的Activity已經(jīng)位于棧頂,那么此Activity不會(huì)被重新創(chuàng)建,同時(shí)它的onNewIntent方法被回調(diào),通過此方法的參數(shù)我們可以取出當(dāng)前的請(qǐng)求信息。需要注意,此Activity的onCreate,onStart方法不會(huì)被系統(tǒng)調(diào)用。如果新Activity不在棧頂,那么新Activity任然會(huì)被重新重建。 - 棧內(nèi)復(fù)用模式:singleTask
? ? ? ?這是一種單實(shí)例模式,只要Activity在一個(gè)棧中存在,那么多次啟動(dòng)此Activity都不會(huì)重新創(chuàng)建實(shí)例,系統(tǒng)也會(huì)回調(diào)onNewIntent方法。
例如:當(dāng)前棧內(nèi)情況為ABC,此時(shí)D被以singleTask的模式被啟動(dòng),當(dāng)前棧變?yōu)锳BCD。
如果當(dāng)前棧內(nèi)情況為ADBC,此時(shí)D被以singleTask的模式被啟動(dòng),當(dāng)前棧變?yōu)锳D。 - 單實(shí)例模式:singleInstance
? ? ? ?這是一種加強(qiáng)的單實(shí)例模式,它除了具有singleTask模式的所有特性外,還加強(qiáng)了一點(diǎn),那就是具有此種模式的Activity只能單獨(dú)位于一個(gè)任務(wù)棧中,比如Activity A是singleInstance模式,A被啟動(dòng)時(shí)系統(tǒng)會(huì)為它創(chuàng)建一個(gè)新的任務(wù)棧,A運(yùn)行在這個(gè)單獨(dú)的任務(wù)棧中,后續(xù)的請(qǐng)求均不會(huì)再創(chuàng)建A,除非這個(gè)單獨(dú)的任務(wù)棧被系統(tǒng)銷毀了。
二 Fragment
1. 為什么Fragment被稱為第五大組件?
Android中的四大組件為Activity,service,ContentProvider,Broadcast。
Fragment因?yàn)橛猩芷冢褂妙l率不輸于四大組件,可靈活加載到Activity中。
1.1 Fragment加載到Activity的兩種方式
- 靜態(tài)加載:直接在Activity布局文件中指定Fragment。代碼如下
<fragment
android:name="com.example.myfragment.MyFragment"
android:id="@+id/myfragment_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- 動(dòng)態(tài)加載:動(dòng)態(tài)加載需要使用到FragmentManager,這種加載方式在開發(fā)中是非常常見的,示例代碼如下:
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
//將FragmentA從容器中移除掉,減少內(nèi)存的消耗
fragmentTransaction.remove(fragmentA);
fragmentTransaction.add(R.id.fragment_layout,new FragmentB());
fragmentTransaction.commit();
1.2 Fragment 與ViewPager搭配使用
? ? ? ?通常情況下我們開發(fā)應(yīng)用最常見的使用情況是TabLayout+ViewPager+Fragment的使用方式,這就涉及到兩個(gè)常用的適配器的使用,一個(gè)是FragmentPagerAdapter,另外一個(gè)是FragmentStatePagerAdapter,那么它們之間有什么區(qū)別呢?其實(shí)很簡單,F(xiàn)ragmentPagerAdapter適用于頁面較少的情況,而FragmentStatePagerAdapter適用于頁面較多的情況。
2. Fragment的生命周期
Fragment
界面打開
onCreate() 方法執(zhí)行!
onCreateView() 方法執(zhí)行!
onActivityCreated() 方法執(zhí)行!
onStart() 方法執(zhí)行!
onResume() 方法執(zhí)行!
按下主屏幕鍵/鎖屏
onPause() 方法執(zhí)行!
onStop() 方法執(zhí)行!
重新打開
onStart() 方法執(zhí)行!
onResume() 方法執(zhí)行!
按下后退鍵
onPause() 方法執(zhí)行!
onStop() 方法執(zhí)行!
onDestroyView() 方法執(zhí)行!
onDestroy() 方法執(zhí)行!
onDetach() 方法執(zhí)行!
Activity
打開應(yīng)用
onCreate() 方法執(zhí)行!
onStart() 方法執(zhí)行!
onResume() 方法執(zhí)行!
按下主屏幕鍵/鎖屏
onPause() 方法執(zhí)行!
onStop() 方法執(zhí)行!
重新打開應(yīng)用
onRestart() 方法執(zhí)行!
onStart() 方法執(zhí)行!
onResume() 方法執(zhí)行!
按下后退鍵
onPause() 方法執(zhí)行!
onStop() 方法執(zhí)行!
onDestroy() 方法執(zhí)行!
在Activity中加入Fragment,對(duì)應(yīng)的生命周期
打開
Fragment onAttach()方法執(zhí)行
Fragment onCreate() 方法執(zhí)行!
Fragment onCreateView() 方法執(zhí)行!
Fragment onViewCreated()方法執(zhí)行
Activity onCreate() 方法執(zhí)行!
Fragment onActivityCreated() 方法執(zhí)行!
Activity onStart() 方法執(zhí)行!
Fragment onStart() 方法執(zhí)行!
Activity onResume() 方法執(zhí)行!
Fragment onResume() 方法執(zhí)行!
按下主屏幕鍵/鎖屏
Fragment onPause() 方法執(zhí)行!
Activity onPause() 方法執(zhí)行!
Fragment onStop() 方法執(zhí)行!
Activity onStop() 方法執(zhí)行!
再次打開
Activity onRestart() 方法執(zhí)行!
Activity onStart() 方法執(zhí)行!
Fragment onStart() 方法執(zhí)行!
Activity onResume() 方法執(zhí)行!
Fragment onResume() 方法執(zhí)行!
按下后退鍵
Fragment onPause() 方法執(zhí)行!
Activity onPause() 方法執(zhí)行!
Fragment onStop() 方法執(zhí)行!
Activity onStop() 方法執(zhí)行!
Fragment onDestroyView() 方法執(zhí)行!
Fragment onDestroy() 方法執(zhí)行!
Fragment onDetach() 方法執(zhí)行!
Activity onDestroy() 方法執(zhí)行!
3. Fragment的通信
3.1 在Fragment中調(diào)用Activity中的方法
? ? ? ?在Fragment中調(diào)用Activity的方法很簡單,F(xiàn)ragment有個(gè)getActivity()的方法,比如,在MainActivity中的一個(gè)Fragment中獲取MainActivity的引用,并調(diào)用MainActivity的某個(gè)方法methodA()方法你可以這么寫:
MainActivity mainActivity = (MainActivity) getActivity();
mainActivity.methodA();
3.2 在Activity中調(diào)用Fragment的方法
? ? ? ?在Activity中調(diào)用Fragment中的方法是最簡單的,我想這里我不用多說吧!直接接口回調(diào)即可調(diào)用Fragment的任何可訪問的方法。
3.3 在Fragment中調(diào)用另外一個(gè)Fragment的方法
? ? ? ?這個(gè)可就需要一定的思維性了,首先要想調(diào)用Fragment A的方法,除了這個(gè)Fragment A自身可以調(diào)用外,這個(gè)Fragment A所屬的Activity也可以調(diào)用,要想另外一個(gè)Fragment B調(diào)用此Fragment A的方法,F(xiàn)ragment B可以間接通過Activity來進(jìn)行調(diào)用,也就是3.1 和 3.2 的結(jié)合。
三 Service
1. Service基礎(chǔ)知識(shí)
1.1 Service是什么?
? ? ? ?Service(服務(wù))是一個(gè)一種可以在后臺(tái)執(zhí)行長時(shí)間運(yùn)行操作而沒有用戶界面的組件。它運(yùn)行于UI線程,因此不能進(jìn)行耗時(shí)的操作。
1.2 Service和Thread的區(qū)別
? ? ? ?Service的運(yùn)行是在UI線程當(dāng)中的,是絕對(duì)絕對(duì)不能進(jìn)行耗時(shí)操作的,而Thread開啟的子線程則可以進(jìn)行耗時(shí)操作,但是Thread開啟的子線程是不能直接對(duì)UI進(jìn)行操作的,否則極有可能發(fā)生直接讓程序崩掉,這就是它們的區(qū)別。
2. 啟動(dòng)Service的2種方式
2.1 startService()方法開啟Service
步驟:
??a.定義一個(gè)類繼承Service。
??b.在AndroidManifest.xml文件中配置該Service。
??c.使用Context的startService(Intent)方法啟動(dòng)該Service。
??d.不再使用該Service時(shí),調(diào)用Context的stopService(Intent)方法停止該Service。
2.2 bindService方法開啟Service(Activity與Service綁定)
步驟:
??a.創(chuàng)建BinderService服務(wù)端,繼承自Service并在類中創(chuàng)建一個(gè)實(shí)現(xiàn)IBinder接口的實(shí)現(xiàn)實(shí)例對(duì)象并提供公共方法給客戶端調(diào)用。
??b.從onBind()回調(diào)方法返回此Binder實(shí)例。
??c.在客戶端中,從onServiceConnected回調(diào)方法接收Binder,并使用提供的方法調(diào)用綁定服務(wù)。
3. Service的生命周期
? ? ? ?服務(wù)的生命周期有兩種,因?yàn)榉?wù)可以跟Activity綁定起來,也可以不綁定,Activity和服務(wù)進(jìn)行通信的話,是需要把服務(wù)和Activity進(jìn)行綁定的。因此服務(wù)的生命周期分為未綁定Activity的和綁定Activity的。
沒有綁定Activity的服務(wù)生命周期:
啟動(dòng)服務(wù)>onCreate()>onStartCommand()>服務(wù)運(yùn)行>onDestory()>服務(wù)銷毀
綁定Activity的服務(wù)生命周期
綁定服務(wù)>onCreate()>onBind()>服務(wù)運(yùn)行>onUnBind()>onDestory()>服務(wù)被銷毀
通過Intent和startService()方法啟動(dòng)了一個(gè)服務(wù),接下來執(zhí)行onCreate()方法,首次創(chuàng)建服務(wù)時(shí),系統(tǒng)將調(diào)用此方法來執(zhí)行一次性設(shè)置程序(在調(diào)用 onStartCommand() 或 onBind() 之前)。如果服務(wù)已在運(yùn)行,則不會(huì)調(diào)用此方法。
當(dāng)另一個(gè)組件(如 Activity)通過調(diào)用 startService() 請(qǐng)求啟動(dòng)服務(wù)時(shí),系統(tǒng)將調(diào)用此方法。一旦執(zhí)行此方法,服務(wù)即會(huì)啟動(dòng)并可在后臺(tái)無限期運(yùn)行。 如果您實(shí)現(xiàn)此方法,則在服務(wù)工作完成后,需要由您通過調(diào)用 stopSelf() 或 stopService() 來停止服務(wù)。(如果您只想提供綁定,則無需實(shí)現(xiàn)此方法。)
服務(wù)開始處于運(yùn)行狀態(tài)。
某個(gè)操作導(dǎo)致服務(wù)停止,比如執(zhí)行了方法stopService(),那么服務(wù)接下來會(huì)執(zhí)行onDestory()銷毀。服務(wù)應(yīng)該實(shí)現(xiàn)此方法來清理所有資源,如線程、注冊(cè)的偵聽器、接收器等。 這是服務(wù)接收的最后一個(gè)調(diào)用。
服務(wù)被完全銷毀,下一步就是等待被垃圾回收器回收了。
通過Intent和bindService()方法啟動(dòng)了一個(gè)服務(wù),接下來會(huì)執(zhí)行onCreate()方法,首次創(chuàng)建服務(wù)時(shí),系統(tǒng)將調(diào)用此方法來執(zhí)行一次性設(shè)置程序(在調(diào)用 onStartCommand() 或 onBind() 之前)。如果服務(wù)已在運(yùn)行,則不會(huì)調(diào)用此方法。
當(dāng)另一個(gè)組件想通過調(diào)用 bindService() 與服務(wù)綁定(例如執(zhí)行 RPC)時(shí),系統(tǒng)將調(diào)用此方法。在此方法的實(shí)現(xiàn)中,您必須通過返回 IBinder 提供一個(gè)接口,供客戶端用來與服務(wù)進(jìn)行通信。請(qǐng)務(wù)必實(shí)現(xiàn)此方法,但如果您并不希望允許綁定,則應(yīng)返回 null。
服務(wù)開始處于運(yùn)行狀態(tài)。成功與Activity綁定。
某個(gè)操作導(dǎo)致服務(wù)解除綁定,比如執(zhí)行了方法unbindService(),那么服務(wù)接下來會(huì)解除與當(dāng)前Activity的綁定。接下來服務(wù)將面臨銷毀。
服務(wù)執(zhí)行onDestory()方法被銷毀。服務(wù)應(yīng)該實(shí)現(xiàn)此方法來清理所有資源,如線程、注冊(cè)的偵聽器、接收器等。 這是服務(wù)接收的最后一個(gè)調(diào)用。
服務(wù)被完全銷毀,下一步就是等待被垃圾回收器回收了。
Service總結(jié):
被啟動(dòng)的服務(wù)的生命周期:如果一個(gè)Service被某個(gè)Activity 調(diào)用 Context.startService 方法啟動(dòng),那么不管是否有Activity使用bindService綁定或unbindService解除綁定到該Service,該Service都在后臺(tái)運(yùn)行。如果一個(gè)Service被startService 方法多次啟動(dòng),那么onCreate方法只會(huì)調(diào)用一次,onStart將會(huì)被調(diào)用多次(對(duì)應(yīng)調(diào)用startService的次數(shù)),并且系統(tǒng)只會(huì)創(chuàng)建Service的一個(gè)實(shí)例(因此你應(yīng)該知道只需要一次stopService調(diào)用)。該Service將會(huì)一直在后臺(tái)運(yùn)行,而不管對(duì)應(yīng)程序的Activity是否在運(yùn)行,直到被調(diào)用stopService,或自身的stopSelf方法。當(dāng)然如果系統(tǒng)資源不足,android系統(tǒng)也可能結(jié)束服務(wù)。
被綁定的服務(wù)的生命周期:如果一個(gè)Service被某個(gè)Activity 調(diào)用 Context.bindService 方法綁定啟動(dòng),不管調(diào)用 bindService 調(diào)用幾次,onCreate方法都只會(huì)調(diào)用一次,同時(shí)onStart方法始終不會(huì)被調(diào)用。當(dāng)連接建立之后,Service將會(huì)一直運(yùn)行,除非調(diào)用Context.unbindService 斷開連接或者之前調(diào)用bindService 的 Context 不存在了(如Activity被finish的時(shí)候),系統(tǒng)將會(huì)自動(dòng)停止Service,對(duì)應(yīng)onDestroy將被調(diào)用。
被啟動(dòng)又被綁定的服務(wù)的生命周期:如果一個(gè)Service又被啟動(dòng)又被綁定,則該Service將會(huì)一直在后臺(tái)運(yùn)行。并且不管如何調(diào)用,onCreate始終只會(huì)調(diào)用一次,對(duì)應(yīng)startService調(diào)用多少次,Service的onStart便會(huì)調(diào)用多少次。調(diào)用unbindService將不會(huì)停止Service,而必須調(diào)用 stopService 或 Service的 stopSelf 來停止服務(wù)。
當(dāng)服務(wù)被停止時(shí)清除服務(wù):當(dāng)一個(gè)Service被終止(1、調(diào)用stopService;2、調(diào)用stopSelf;3、不再有綁定的連接(沒有被啟動(dòng)))時(shí),onDestroy方法將會(huì)被調(diào)用,在這里你應(yīng)當(dāng)做一些清除工作,如停止在Service中創(chuàng)建并運(yùn)行的線程。
四 Broadcast
1. 廣播的概念
1.1 定義
在Android中,它是一種廣泛運(yùn)用在應(yīng)用程序之間傳輸信息的機(jī)制,Android中我們發(fā)送廣播內(nèi)容是一個(gè)Intent,這個(gè)Intent中可以攜帶我們要發(fā)送的數(shù)據(jù)。
1.2 廣播的使用場景
a.同一app內(nèi)有多個(gè)進(jìn)程的不同組件之間的消息通信。
b.不同app之間的組件之間消息的通信。
1.3 廣播的種類
標(biāo)準(zhǔn)廣播:context.sendBroadcast(Intent)方法發(fā)送的廣播,不可被攔截
有序廣播:context.sendOrderBroadcast(Intent)方法發(fā)送的廣播,可被攔截
本地廣播:localBroadcastManager.sendBroadcast(Intent),只在app內(nèi)傳播
2. 廣播接收器
廣播接收器是專門用來接收廣播信息的,它可分為靜態(tài)注冊(cè)和動(dòng)態(tài)注冊(cè):
- 靜態(tài)注冊(cè):注冊(cè)完成一直在運(yùn)行。
首先你要?jiǎng)?chuàng)建一個(gè)廣播接收器類,實(shí)例代碼如下:
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();
}
}
另外,靜態(tài)的廣播接收器一定要在AndroidManifest.xml文件中注冊(cè)才可以使用,AndroidManifest.xml文件中注冊(cè)靜態(tài)廣播代碼如下:
<receiver
android:name=".BootCompleteReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
- 動(dòng)態(tài)注冊(cè):跟隨Activity的生命周期。
新建一個(gè)類,讓它繼承自BroadcastReceiver,并重寫父類的onReceive()方法就行了。這樣有廣播到來時(shí),onReceive()方法就會(huì)得到執(zhí)行,具體的邏輯就可以在這個(gè)方法中處理。 - 動(dòng)態(tài)注冊(cè)廣播接收器的優(yōu)點(diǎn)以及缺點(diǎn):
動(dòng)態(tài)注冊(cè)的廣播接收器可以自由地控制注冊(cè)與注銷,在靈活性方面有很大優(yōu)勢,但是它也存在著一個(gè)缺點(diǎn),即必須要在程序啟動(dòng)之后才能接收到廣播,因?yàn)樽?cè)的邏輯是寫在onCreate()方法中的。那么有沒有廣播能在程序未啟動(dòng)的情況下就能接收到廣播呢?靜態(tài)注冊(cè)的廣播接收器就可以做到。
3. 廣播內(nèi)部實(shí)現(xiàn)機(jī)制
自定義廣播接收者BroadcastReceiver,并且重寫onReceiver()方法。
通過Binder機(jī)制向AMS(Activity Manager Service)進(jìn)行注冊(cè)。
廣播發(fā)送者通過Binder機(jī)制向AMS發(fā)送廣播。
AMS查找符合條件(IntentFilter/Permission等)的BroadcastReceiver,將廣播發(fā)送到相應(yīng)的BroadcastReceiver(一般情況下是Activity)的消息隊(duì)列中。
消息循環(huán)執(zhí)行拿到此廣播,回調(diào)BroadcastReceiver中的onReceiver()方法。
4. 本地廣播
本地廣播的發(fā)送和注冊(cè)廣播接收器都需要使用到LocalBroadcastManager類,如下所示為本地廣播的發(fā)送和本地廣播接收器注冊(cè)的代碼:
??本地廣播的發(fā)送:
public static void sendLocalBroadcast(Context context,String action){
Intent intent = new Intent(action);
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
localBroadcastManager.sendBroadcast(intent);
}
本地廣播的接收器的注冊(cè):
IntentFilter intentFilter = new IntentFilter();
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
intentFilter.addAction(new BroadcastUtil().action_next);
nasbr = new NextAndStartBroadcastReceiver();
localBroadcastManager.registerReceiver(nasbr, intentFilter);//注冊(cè)本地廣播接收器
特點(diǎn):
1. 使用它發(fā)送的廣播將只在自身app內(nèi)傳播,因此你不必?fù)?dān)心泄漏隱私的數(shù)據(jù)。
2. 其他app無法對(duì)你的app發(fā)送該廣播,因此你的app根本不可能收到非自身app發(fā)送的該廣播,因此你不必?fù)?dān)心有安全漏洞可以利用。
3. 比系統(tǒng)廣播更加高效。
內(nèi)部實(shí)現(xiàn)機(jī)制:
1. LocalBroadcast高效的原因:因?yàn)樗鼉?nèi)部是通過Handler實(shí)現(xiàn)的,它的sendBroadcast()方法含義并非和系統(tǒng)的sendBroadcast()一樣,它的sendBroadcast()方法其實(shí)就是通過Handler發(fā)送了一個(gè)Message而已。
2. LocalBroadcast安全的原因:既然它是通過Handler實(shí)現(xiàn)廣播發(fā)送的,那么相比系統(tǒng)廣播通過Binder機(jī)制實(shí)現(xiàn)那肯定更加高效,同時(shí)使用Handler來實(shí)現(xiàn),別的app無法向我們應(yīng)用發(fā)送該廣播,而我們app內(nèi)部發(fā)送的廣播也不會(huì)離開我們的app。
LocalBroadcast內(nèi)部協(xié)作主要是靠兩個(gè)Map集合:mReceivers和mActions,當(dāng)然還有一個(gè)List集合mPendingBroadcasts,這個(gè)主要存儲(chǔ)待接收的廣播對(duì)象。
五 WebView
1. WebView常見的坑
API 16之前版本存在遠(yuǎn)程代碼執(zhí)行漏洞,該漏洞源自于程序沒有正確限制使用WebView.addJavascriptInterface方法,攻擊者可以使用Java Reflection API利用該漏洞執(zhí)行任意Java對(duì)象和方法。
WebView的銷毀和內(nèi)存泄漏問題。WebView的完全銷毀是件麻煩事,一旦銷毀流程不正確,極易容易導(dǎo)致內(nèi)存泄漏。
jsbridge
??通過javascript構(gòu)建一個(gè)橋,橋的兩端一個(gè)端是客戶端,一端是服務(wù)端,它可以讓本地端調(diào)用遠(yuǎn)端的web代碼,也可以讓遠(yuǎn)端調(diào)用本地的代碼。WebViewClient.onPageFinished(不靠譜) –> WebChromeClient.onProgressChanged(靠譜)
后臺(tái)耗電(性能優(yōu)化)
??WebView開啟網(wǎng)頁時(shí)會(huì)自己開啟線程,如果沒有合理的銷毀,那么殘余線程就會(huì)一直運(yùn)行,so這會(huì)非常耗電的,解決方案:有一種暴力方式就是Activity的onDestroy中調(diào)用System.exit()方法把虛擬機(jī)關(guān)閉,也可以結(jié)合自己應(yīng)用的WebView的情況設(shè)計(jì)出一個(gè)溫柔的方案。WebView硬件加速導(dǎo)致的頁面渲染問題
??WebView硬件加速偶爾導(dǎo)致頁面白塊,頁面閃爍,但是加載速度比較快,解決方案:關(guān)閉硬件加速。
2. WebView的內(nèi)存泄漏問題
原因:WebView會(huì)關(guān)聯(lián)一個(gè)Activity,WebView執(zhí)行的操作是在新線程當(dāng)中回收,時(shí)間Activity沒有辦法確認(rèn),Activity的生命周期和WebView線程生命周期不一致導(dǎo)致WebView一直執(zhí)行,因?yàn)閃ebView內(nèi)部持有Activity的引用,導(dǎo)致Activity一直不能被回收,原理類似于匿名內(nèi)部類持有外部類的引用一樣。那么如何解決呢?解決方案如下:
獨(dú)立進(jìn)程,簡單暴力,涉及到進(jìn)程間通信。(開發(fā)過程中常用)
動(dòng)態(tài)添加WebView,對(duì)傳入WebView中使用Context弱引用,動(dòng)態(tài)添加WebView意思在布局中創(chuàng)建一個(gè)ViewGroup用來放置WebView,Activity創(chuàng)建add進(jìn)來,Activity停止時(shí)remove掉。
六 Bainder機(jī)制
通常情況下,Binder是一種通信機(jī)制。
對(duì)于Server來說,Binder指的是Binder本地對(duì)象/對(duì)于Client來說,Binder指的是Binder的代理對(duì)象。
對(duì)于傳輸過程而言,Binder是可以進(jìn)行跨進(jìn)程傳遞的對(duì)象。
AIDL是Binder機(jī)制的一個(gè)實(shí)例。
七 Handler機(jī)制
1. 定義
Handler是可以通過發(fā)送和處理Message和Runnable對(duì)象來關(guān)聯(lián)相應(yīng)線程的MessageQueue。通常我們認(rèn)為它是一種異步機(jī)制。
- 可以讓對(duì)應(yīng)的Message和Runnable在未來的某個(gè)時(shí)間點(diǎn)進(jìn)行相應(yīng)的處理。
- 讓自己想要的耗時(shí)操作在子線程中完成,讓更新UI的操作在主線程中完成,而子線程與主線程之間的通信就是靠Handler來完成。
2. Handler的使用方法
- post(Runnable)
- sendMessage(Message)
3. Handler內(nèi)部實(shí)現(xiàn)機(jī)制
Handler機(jī)制也可叫異步消息機(jī)制,它主要由4個(gè)部分組成:Message,Handler,MessageQueue,Looper,在上面我們已經(jīng)接觸到了Message和Handler,接下來我們對(duì)4個(gè)成員進(jìn)行著重的了解:
Message
??Message是在線程之間傳遞的消息,它可以在內(nèi)部攜帶少量的信息,用于在不同線程之間交換數(shù)據(jù)。使用Message的arg1和arg2便可攜帶int數(shù)據(jù),使用obj便可攜帶Object類型數(shù)據(jù)。Handler
??Handler顧名思義就是處理者的意思,它只要用于在子線程發(fā)送消息對(duì)象Message,在UI線程處理消息對(duì)象Message,在子線程調(diào)用sendMessage方法發(fā)送消息對(duì)象Message,而發(fā)送的消息經(jīng)過一系列地輾轉(zhuǎn)之后最終會(huì)被傳遞到Handler的handleMessage方法中,最終在handleMessage方法中消息對(duì)象Message被處理。MessageQueue
??MessageQueue就是消息隊(duì)列的意思,它只要用于存放所有通過Handler發(fā)送過來的消息。這部分消息會(huì)一直存放于消息隊(duì)列當(dāng)中,等待被處理。每個(gè)線程中只會(huì)有一個(gè)MessageQueue對(duì)象,請(qǐng)牢記這句話。其實(shí)從字面上就可以看出,MessageQueue底層數(shù)據(jù)結(jié)構(gòu)是隊(duì)列,而且這個(gè)隊(duì)列只存放Message對(duì)象。Looper
??Looper是每個(gè)線程中的MessageQueue的管家,調(diào)用Looper的loop()方法后,就會(huì)進(jìn)入到一個(gè)無限循環(huán)當(dāng)中,然后每當(dāng)MesssageQueue中存在一條消息,Looper就會(huì)將這條消息取出,并將它傳遞到Handler的handleMessage()方法中。每個(gè)線程只有一個(gè)Looper對(duì)象。
Handler機(jī)制流程圖如下:
4. Handler引起的內(nèi)存泄漏以及解決方法
原因:靜態(tài)內(nèi)部類持有外部類的匿名引用,導(dǎo)致外部activity無法得到釋放。
解決方法:handler內(nèi)部持有外部的弱引用,并把handler改為靜態(tài)內(nèi)部類,在activity的onDestory()中調(diào)用handler的removeCallback()方法。
八 IntentService機(jī)制
1.IntentService是什么?
它的優(yōu)先級(jí)高于Service。
??IntentService是繼承處理異步請(qǐng)求的一個(gè)類,在IntentService內(nèi)有一個(gè)工作線程來處理耗時(shí)操作,啟動(dòng)IntentServiced的方式和啟動(dòng)傳統(tǒng)的Service一樣,同時(shí),當(dāng)任務(wù)執(zhí)行完成后,IntentService會(huì)自動(dòng)停止,而不需要我們手動(dòng)去控制或stopSelf()。另外,可以啟動(dòng)IntentService多次,而每一個(gè)耗時(shí)操作會(huì)以工作隊(duì)列的方式在IntentService的onHandlerIntent回調(diào)方法中執(zhí)行,并且,每次只執(zhí)行一個(gè)工作線程,執(zhí)行完第一個(gè)在執(zhí)行第二個(gè)。
- 它本質(zhì)是一種特殊的Service,繼承自Service并且本身就是一個(gè)抽象類。
- 它內(nèi)部是由HandlerThread和Handler實(shí)現(xiàn)異步操作。
2.IntentService的使用方法
創(chuàng)建IntentService時(shí),只需要實(shí)現(xiàn)onHandlerIntent和構(gòu)造方法,onHandlerIntent為異步方法,可以執(zhí)行耗時(shí)操作。
九 AsyncTask 機(jī)制
1.AsyncTask是什么?
它本質(zhì)上是一個(gè)封裝了線程池和Handler的異步框架。
2.AsyncTask的基本用法
- 3個(gè)參數(shù):Params, Progress 和 Result
- Params表示用于AsyncTask執(zhí)行任務(wù)的參數(shù)的類型
- Progress表示在后臺(tái)線程處理的過程中,可以階段性地發(fā)布結(jié)果的數(shù)據(jù)類型
- Result表示任務(wù)全部完成后所返回的數(shù)據(jù)類型
- 5個(gè)方法
- onPreExecute
該方法是運(yùn)行在主線程中的。在AsyncTask執(zhí)行了execute()方法后就會(huì)在UI線程上執(zhí)行onPreExecute()方法,該方法在task真正執(zhí)行前運(yùn)行,我們通常可以在該方法中顯示一個(gè)進(jìn)度條,從而告知用戶后臺(tái)任務(wù)即將開始。 - doInBackground
該方法是運(yùn)行在單獨(dú)的工作線程中的,而不是運(yùn)行在主線程中。 - onProgressUpdate
當(dāng)我們?cè)赿oInBackground中調(diào)用publishProgress(Progress…)方法后,就會(huì)在UI線程上回調(diào)onProgressUpdate方法。 - onPostExecute
當(dāng)doInBackgroud方法執(zhí)行完畢后,就表示任務(wù)完成了,doInBackgroud方法的返回值就會(huì)作為參數(shù)在主線程中傳入到onPostExecute方法中,這樣就可以在主線程中根據(jù)任務(wù)的執(zhí)行結(jié)果更新UI。
3.AsyncTask機(jī)制的原理
它本質(zhì)上是一個(gè)靜態(tài)的線程池,AsyncTask派生出的子類可以實(shí)現(xiàn)不同的異步任務(wù),這些任務(wù)都是提交到靜態(tài)的線程池中執(zhí)行。
線程池中的工作線程執(zhí)行doInBackground(mParams)方法執(zhí)行異步的任務(wù)。
當(dāng)任務(wù)狀態(tài)改變后,工作線程向UI線程發(fā)送消息,AsyncTask內(nèi)部的InternalHandler響應(yīng)這些消息,并調(diào)用相關(guān)的回調(diào)函數(shù)。
4.AsyncTask注意事項(xiàng):
- 內(nèi)存泄漏:
靜態(tài)內(nèi)部類持有外部類的匿名引用,導(dǎo)致外部對(duì)象無法得到釋放,解決方法很簡單,讓內(nèi)部持有外部的弱引用即可解決
生命周期
??在Activity的onDestory()中及時(shí)對(duì)AsyncTask進(jìn)行回收,調(diào)用其cancel()方法來保證程序的穩(wěn)定性。結(jié)果丟失
??當(dāng)內(nèi)存不足時(shí),當(dāng)前的Activity被回收,由于AsyncTask持有的是回收之前Activity的引用,導(dǎo)致AsyncTask更新的結(jié)果對(duì)象為一個(gè)無效的Activity的引用,這就是結(jié)果丟失。并行或串行
??在1.6(Donut)之前: 在第一版的AsyncTask,任務(wù)是串行調(diào)度。一個(gè)任務(wù)執(zhí)行完成另一個(gè)才能執(zhí)行。由于串行執(zhí)行任務(wù),使用多個(gè)AsyncTask可能會(huì)帶來有些問題。所以這并不是一個(gè)很好的處理異步(尤其是需要將結(jié)果作用于UI試圖)操作的方法。1.6-2.3: 所有的任務(wù)并發(fā)執(zhí)行,這會(huì)導(dǎo)致一種情況,就是其中一條任務(wù)執(zhí)行出問題了,會(huì)引起其他任務(wù)出現(xiàn)錯(cuò)誤。3.0之后AsyncTask又修改為了順序執(zhí)行,并且新添加了一個(gè)函數(shù) executeOnExecutor(Executor),如果您需要并行執(zhí)行,則只需要調(diào)用該函數(shù),并把參數(shù)設(shè)置為并行執(zhí)行即可。
十 HandlerThread機(jī)制
1.HandlerThread的產(chǎn)生背景
開啟子線程進(jìn)行耗時(shí)操作,多次創(chuàng)建和銷毀子線程是很耗費(fèi)資源的,但是木有關(guān)系,谷歌考慮了這點(diǎn)為我們專門開發(fā)出了HandlerThread機(jī)制。
2.HandlerThread是什么?
本質(zhì):Handler + Thread + Looper,是一個(gè)Thread內(nèi)部有Looper。
HandlerThread本質(zhì)上是一個(gè)線程類,它繼承了Thread。
HandlerThread有自己內(nèi)部的Looper對(duì)象,可以進(jìn)行Looper循環(huán)。
通過獲取HandlerThread的Looper對(duì)象傳遞給Handler對(duì)象,可以在handlerMessage方法中執(zhí)行異步任務(wù)。
優(yōu)點(diǎn)是不會(huì)有堵塞,減少對(duì)性能的消耗,缺點(diǎn)是不能進(jìn)行多任務(wù)的處理,需要等待進(jìn)行處理,處理效率較低。
與線程池注重并發(fā)不同,HandlerThread是一個(gè)串行隊(duì)列,HandlerThread背后只有一個(gè)線程。
十一 IntentService機(jī)制
1.IntentService是什么?
它的優(yōu)先級(jí)高于Service。
??IntentService是繼承處理異步請(qǐng)求的一個(gè)類,在IntentService內(nèi)有一個(gè)工作線程來處理耗時(shí)操作,啟動(dòng)IntentServiced的方式和啟動(dòng)傳統(tǒng)的Service一樣,同時(shí),當(dāng)任務(wù)執(zhí)行完成后,IntentService會(huì)自動(dòng)停止,而不需要我們手動(dòng)去控制或stopSelf()。另外,可以啟動(dòng)IntentService多次,而每一個(gè)耗時(shí)操作會(huì)以工作隊(duì)列的方式在IntentService的onHandlerIntent回調(diào)方法中執(zhí)行,并且,每次只執(zhí)行一個(gè)工作線程,執(zhí)行完第一個(gè)在執(zhí)行第二個(gè)。
它本質(zhì)是一種特殊的Service,繼承自Service并且本身就是一個(gè)抽象類。
它內(nèi)部是由HandlerThread和Handler實(shí)現(xiàn)異步操作。
2.IntentService的使用方法
創(chuàng)建IntentService時(shí),只需要實(shí)現(xiàn)onHandlerIntent和構(gòu)造方法,onHandlerIntent為異步方法,可以執(zhí)行耗時(shí)操作。
十二 View繪制機(jī)制
1. View樹的繪制流程
measure(測量)→layout(布局)→draw(繪制)
2. Measure過程
measure過程主要就是從頂層父View向子View遞歸調(diào)用view.measure方法(measure中又回調(diào)onMeasure方法)的過程。具體measure核心主要有如下幾點(diǎn):
MeasureSpec(View的內(nèi)部類)測量規(guī)格為int型,值由高2位規(guī)格模式specMode和低30位具體尺寸specSize組成。其中specMode只有三種值:
- MeasureSpec.EXACTLY //確定模式,父View希望子View的大小是確定的,由specSize決定;
- MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;
- MeasureSpec.UNSPECIFIED //未指定模式,父View完全依據(jù)子View的設(shè)計(jì)值來決定;
View的measure方法是final的,不允許重載,View子類只能重載onMeasure來完成自己的測量邏輯。
最頂層DecorView測量時(shí)的MeasureSpec是由ViewRootImpl中g(shù)etRootMeasureSpec方法確定的(LayoutParams寬高參數(shù)均為MATCH_PARENT,specMode是EXACTLY,specSize為物理屏幕大小)。
ViewGroup類提供了measureChild,measureChild和measureChildWithMargins方法,簡化了父子View的尺寸計(jì)算。
只要是ViewGroup的子類就必須要求LayoutParams繼承子MarginLayoutParams,否則無法使用layout_margin參數(shù)。
View的布局大小由父View和子View共同決定。
使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個(gè)方法在onMeasure流程之后被調(diào)用才能返回有效值。
3. Layout過程
整個(gè)layout過程比較容易理解,從上面分析可以看出layout也是從頂層父View向子View的遞歸調(diào)用view.layout方法的過程,即父View根據(jù)上一步measure子View所得到的布局大小和布局參數(shù),將子View放在合適的位置上。具體layout核心主要有以下幾點(diǎn):
View.layout方法可被重載,ViewGroup.layout為final的不可重載,ViewGroup.onLayout為abstract的,子類必須重載實(shí)現(xiàn)自己的位置邏輯。
measure操作完成后得到的是對(duì)每個(gè)View經(jīng)測量過的measuredWidth和measuredHeight,layout操作完成之后得到的是對(duì)每個(gè)View進(jìn)行位置分配后的mLeft、mTop、mRight、mBottom,這些值都是相對(duì)于父View來說的。
凡是layout_XXX的布局屬性基本都針對(duì)的是包含子View的ViewGroup的,當(dāng)對(duì)一個(gè)沒有父容器的View設(shè)置相關(guān)layout_XXX屬性是沒有任何意義的(前面《Android應(yīng)用setContentView與LayoutInflater加載解析機(jī)制源碼分析》也有提到過)。
使用View的getWidth()和getHeight()方法來獲取View測量的寬高,必須保證這兩個(gè)方法在onLayout流程之后被調(diào)用才能返回有效值。
4.Draw過程
繪制過程就是把View對(duì)象繪制到屏幕上,整個(gè)draw過程需要注意如下細(xì)節(jié):
如果該View是一個(gè)ViewGroup,則需要遞歸繪制其所包含的所有子View。
View默認(rèn)不會(huì)繪制任何內(nèi)容,真正的繪制都需要自己在子類中實(shí)現(xiàn)。
View的繪制是借助onDraw方法傳入的Canvas類來進(jìn)行的。
區(qū)分View動(dòng)畫和ViewGroup布局動(dòng)畫,前者指的是View自身的動(dòng)畫,可以通過setAnimation添加,后者是專門針對(duì)ViewGroup顯示內(nèi)部子視圖時(shí)設(shè)置的動(dòng)畫,可以在xml布局文件中對(duì)ViewGroup設(shè)置layoutAnimation屬性(譬如對(duì)LinearLayout設(shè)置子View在顯示時(shí)出現(xiàn)逐行、隨機(jī)、下等顯示等不同動(dòng)畫效果)。
在獲取畫布剪切區(qū)(每個(gè)View的draw中傳入的Canvas)時(shí)會(huì)自動(dòng)處理掉padding,子View獲取Canvas不用關(guān)注這些邏輯,只用關(guān)心如何繪制即可。
默認(rèn)情況下子View的ViewGroup.drawChild繪制順序和子View被添加的順序一致,但是你也可以重載ViewGroup.getChildDrawingOrder()方法提供不同順序。
十三 Android部分事件分發(fā)機(jī)制
1. 為什么有事件分發(fā)機(jī)制
Android上面的View是樹形結(jié)構(gòu),View可能會(huì)重疊在一起,當(dāng)我們點(diǎn)擊的地方有多個(gè)View都可以響應(yīng)的時(shí)候,這個(gè)點(diǎn)擊事件應(yīng)該給誰呢?為了解決這個(gè)問題,就有了事件分發(fā)機(jī)制。
2. 3個(gè)重要的有關(guān)事件分發(fā)的方法
-
dispatch TouchEvent
用來進(jìn)行事件的分發(fā)。如果事件能夠傳遞給當(dāng)前View,那么此方法一定會(huì)被調(diào)用,返回結(jié)果受當(dāng)前View的onTouchEvent和下級(jí)的dispatchTouchEvent方法影響,表示是否消耗此事件。 -
onInterceptTouchEvent
在上述方法dispatchTouchEvent內(nèi)部調(diào)用,用來判斷是否攔截某個(gè)事件,如果當(dāng)前View攔截了某個(gè)事件,那么同一個(gè)事件序列當(dāng)中,此方法不會(huì)被再次調(diào)用,返回結(jié)果表示是否攔截當(dāng)前事件。 -
onTouchEvent
同樣也會(huì)在dispatchTouchEvent內(nèi)部調(diào)用,用來處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件,如果不消耗,則在同一個(gè)事件序列中,當(dāng)前View無法再次接收到事件。
偽代碼
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;//記錄返回值
if(onInterceptTouchEvent(ev)){//判斷是否攔截此事件
consume = onTouchEvent(ev);//如果當(dāng)前確認(rèn)攔截此事件,那么就處理這個(gè)事件
}else{
consume = child.dispatchToucnEvent(ev);//如果當(dāng)前確認(rèn)不攔截此事件,那么就將事件分發(fā)給下一級(jí)
}
return consume;
}
通過上述偽代碼,我們可以得知點(diǎn)擊事件的傳遞規(guī)則:對(duì)于一個(gè)根ViewGroup而言,點(diǎn)擊事件產(chǎn)生后,首先會(huì)傳遞給它,這時(shí)它的dispatchTouch就會(huì)被調(diào)用,如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當(dāng)前的事件,接著事件就會(huì)交給這個(gè)ViewGroup處理,即它的onTouch方法就會(huì)被調(diào)用;如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回false就表示它不攔截當(dāng)前事件,這時(shí)當(dāng)前事件就會(huì)繼續(xù)傳遞給它的子元素,接著子元素的dispatchTouchEvent方法就會(huì)被調(diào)用,如此直到事件被最終處理。
當(dāng)一個(gè)View需要處理事件時(shí),如果它設(shè)置了OnTouchListener,那么OnTouchListener中的onTouch方法會(huì)被回調(diào)。這時(shí)事件處理還要看onTouch的返回值,如果返回false,則當(dāng)前View的onTouchEvent方法會(huì)被調(diào)用;如果返回true,那么當(dāng)前View的onTouchEvent方法不會(huì)被調(diào)用。由此可見,給View設(shè)置的onTouchListener的優(yōu)先級(jí)比onTouchEvent要高。在onTouchEvent方法中,如果當(dāng)前設(shè)置的有onClickListener,那么它的onClick方法會(huì)被調(diào)用。可以看出,平時(shí)我們常用的OnClickListener,其優(yōu)先級(jí)最低,即處于事件傳遞的尾端。
當(dāng)一個(gè)點(diǎn)擊事件產(chǎn)生后,它的傳遞過程遵循如下順序:Activity–>Window–>View,即事件總數(shù)先傳遞給Activity,Activity再傳遞給Window,最后Window再傳遞給頂級(jí)View,頂級(jí)View接收到事件后,就會(huì)按照事件分發(fā)機(jī)制去分發(fā)事件。考慮一種情況,如果一個(gè)View的onTouchEvent返回false,那么它的父容器的onTouchEvent將會(huì)被調(diào)用,依次類推。如果所有的元素都不處理這個(gè)事件,那么這個(gè)事件將會(huì)最終傳遞給Activity處理, 即Activity的onTouchEvent方法會(huì)被調(diào)用。這個(gè)過程其實(shí)很好理解,我們可以換一種思路,假設(shè)點(diǎn)擊事件是一個(gè)難題,這個(gè)難題最終被上級(jí)領(lǐng)導(dǎo)分給了一個(gè)程序員去處理(這是事件分發(fā)過程),結(jié)果這個(gè)程序員搞不定(onTouchEvent返回了false),現(xiàn)在該怎么辦呢?難題必須要解決,那就只能交給水平更高的上級(jí)解決(上級(jí)的onTouchEvent被調(diào)用),如果上級(jí)再搞不定,那就只能交給上級(jí)的上級(jí)去解決,就這樣難題一層層地向上拋,這是公司內(nèi)部一種常見的處理問題的過程。
關(guān)于事件傳遞機(jī)制還需要注意以下:
同一見事件序列是從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結(jié)束,在這個(gè)過程中所產(chǎn)生的一系列事件,這個(gè)事件的序列以down開始,中間含有數(shù)量不定的move事件,最終以u(píng)p事件結(jié)束。
正常情況下,一個(gè)事件序列只能被一個(gè)View攔截且消耗。這一條的原因可以參考(3),因?yàn)橐坏┮粋€(gè)元素?cái)r截了某個(gè)事件,那么同一個(gè)事件序列的所有事件都會(huì)直接交給它處理,因此同一個(gè)事件序列中的事件不能分別由兩個(gè)View同時(shí)處理,但是通過特殊手段可以做到,比如
一個(gè)View將本該自己處理的事件通過onTouchEvent強(qiáng)行傳遞給其他View處理。某個(gè)View一旦決定攔截,那么這個(gè)事件序列都只能由它來處理(如果事件序列能夠傳遞給它的話),并且它的onInterceptTouchEvent不會(huì)被調(diào)用。這條也很好理解,就是說當(dāng)一個(gè)View決定攔截一個(gè)事件后,那么系統(tǒng)會(huì)把同一個(gè)事件序列內(nèi)的其他方法都直接交給它來處理,因此就不用再調(diào)用這個(gè)View的onInterceptTouchEvent去詢問它是否攔截了。
某個(gè)View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一件序列中的其他事件都不會(huì)再交給它處理,并且事件 將重新交由它的父元素去處理,即父元素的onTouchEvent會(huì)被調(diào)用。意思就是事件一旦交給一個(gè)View處理,那么它就必須消耗掉,否則同一事件序列中剩下的事件就不再交給它處理了,這就好比上級(jí)交給程序員一件事,如果這件事沒有處理好,短時(shí)間內(nèi)上級(jí)就不敢再把事件交給這個(gè)程序員做了,二者是類似的道理。
如果View不消耗ACTION_DOWN以外的事件,那么這個(gè)點(diǎn)擊事件會(huì)消失,此時(shí)父元素的onTouchEvent并不會(huì)調(diào)用,并且當(dāng)前View可以持續(xù)收到后續(xù)的事件,最終這些消失的點(diǎn)擊事件會(huì)傳遞給Activity處理。
ViewGroup默認(rèn)不攔截任何事件。Android源碼中ViewGroup的onInterceptTouchEvent方法默認(rèn)返回false。
View沒有onInterceptTouchEvent方法,一旦點(diǎn)擊事件傳遞給它,那么它的onTouchEvent方法就會(huì)被調(diào)用。
View的onTouchEvent默認(rèn)都會(huì)消耗事件(返回true),除非它是不可點(diǎn)擊的(clickable和longClickable同時(shí)為false)。View的longClickable屬性默認(rèn)為false,clickable屬性要分情況,比如Button的clickable屬性默認(rèn)為true,而TextView的clickable屬性默認(rèn)為false。
View的enable屬性不影響onTouchEvent的默認(rèn)返回值。哪怕一個(gè)View是disable狀態(tài)的,只要它的clickable或者longClickable有一個(gè)為true,那么它的onTouchEvent就返回true。
onClick會(huì)發(fā)生的前提是當(dāng)前View是可點(diǎn)擊的,并且它接收到了down和up事件。
事件傳遞過程是由外向內(nèi)的,即事件總是先傳遞給父元素,然后再由父元素分發(fā)給子View,通過requestDisallowInterTouchEvent方法可以在子元素中干預(yù)父元素的事件分發(fā)過程,但是ACTION_DOWN事件除外。
3. 事件分發(fā)的流程
Activity–>PhoneWindow–>DecorView–>ViewGroup–>…–>View(View樹最底部的View)
十三 ListView
1. ListView是什么?
ListView就是能用一個(gè)數(shù)據(jù)集合以動(dòng)態(tài)滾動(dòng)的方式展示到用戶界面上的View。
2. ListView的適配器
- ArrayAdapter:支持泛型操作,最簡單的一個(gè)Adapter,只能展現(xiàn)一行文字
- SimpleAdapter:同樣具有良好擴(kuò)展性的一個(gè)Adapter,可以自定義多種效果
- BaseAdapter:抽象類,實(shí)際開發(fā)中我們會(huì)繼承這個(gè)類并且重寫相關(guān)方法,用得最多的一個(gè)Adapter
BaseAdapter是開發(fā)中最常用的適配器ArrayAdapter, SimpleAdapter 都繼承于BaseAdapter。BaseAdapter可以完成自己定義的Adapter,可以將任何復(fù)雜組合的數(shù)據(jù)和資源,以任何你想要的顯示效果展示給用戶。
繼承BaseAdapter之后,需要重寫以下四個(gè)方法:getCount,getItem,getItemId,getView。
系統(tǒng)在繪制ListView之前,將會(huì)先調(diào)用getCount方法來獲取Item的個(gè)數(shù)。每繪制一個(gè)Item就會(huì)調(diào)用一次getView方法,在getView中引用事先定義好的layout布局確定顯示的效果并返回一個(gè)View對(duì)象作為一個(gè)Item顯示出來。
這兩個(gè)方法是自定ListView顯示效果中最為重要的,同時(shí)只要重寫好了這兩個(gè)方法,ListView就能完全按開發(fā)者的要求顯示。而getItem和getItemId方法將會(huì)在調(diào)用ListView的響應(yīng)方法的時(shí)候被調(diào)用到。
3. ListView的recycleBin機(jī)制
在某一時(shí)刻,我們看到ListView中有許多View呈現(xiàn)在UI上,這些View對(duì)我們來說是可見的,這些可見的View可以稱作OnScreen的View,即在屏幕中能看到的View,也可以叫做ActiveView,因?yàn)樗鼈兪窃赨I上可操作的。
當(dāng)觸摸ListView并向上滑動(dòng)時(shí),ListView上部的一些OnScreen的View位置上移,并移除了ListView的屏幕范圍,此時(shí)這些OnScreen的View就變得不可見了,不可見的View叫做OffScreen的View,即這些View已經(jīng)不在屏幕可見范圍內(nèi)了,也可以叫做ScrapView,Scrap表示廢棄的意思,ScrapView的意思是這些OffScreen的View不再處于可以交互的Active狀態(tài)了。ListView會(huì)把那些ScrapView(即OffScreen的View)刪除,這樣就不用繪制這些本來就不可見的View了,同時(shí),ListView會(huì)把這些刪除的ScrapView放入到RecycleBin中存起來,就像把暫時(shí)無用的資源放到回收站一樣。
當(dāng)ListView的底部需要顯示新的View的時(shí)候,會(huì)從RecycleBin中取出一個(gè)ScrapView,將其作為convertView參數(shù)傳遞給Adapter的getView方法,從而達(dá)到View復(fù)用的目的,這樣就不必在Adapter的getView方法中執(zhí)行LayoutInflater.inflate()方法了。
RecycleBin中有兩個(gè)重要的View數(shù)組,分別是mActiveViews和mScrapViews。這兩個(gè)數(shù)組中所存儲(chǔ)的View都是用來復(fù)用的,只不過mActiveViews中存儲(chǔ)的是OnScreen的View,這些View很有可能被直接復(fù)用;而mScrapViews中存儲(chǔ)的是OffScreen的View,這些View主要是用來間接復(fù)用的。
4. ListView優(yōu)化
convertView重用機(jī)制
ViewHolder機(jī)制
三級(jí)緩沖/滑動(dòng)監(jiān)聽事件
優(yōu)化一:在Adapter中的getView方法中使用ConvertView,即ConvertView的復(fù)用,不需要每次都inflate一個(gè)View出來,這樣既浪費(fèi)時(shí)間,又浪費(fèi)內(nèi)存。
優(yōu)化二:使用ViewHolder,不要在getView方法中寫findViewById方法,因?yàn)間etView方法會(huì)執(zhí)行很多遍,這樣也可以節(jié)省時(shí)間,節(jié)約內(nèi)存。
優(yōu)化三:使用分頁加載,講真實(shí)際開發(fā)中,ListView的數(shù)據(jù)肯定不止幾百條,成千上萬條數(shù)據(jù)你不可能一次性加載出來,所以這里需要用到分頁加載,一次加載幾條或者十幾條,但是如果數(shù)據(jù)量很大的話,像qq,微信這種,如果順利加載到最后面的話,那么你的list中也會(huì)有幾萬甚至幾十萬的數(shù)據(jù),這樣可能也會(huì)導(dǎo)致OOM,所以你的數(shù)據(jù)集List中也不能有那么多數(shù)據(jù),所以每加載一頁的時(shí)候你可以覆蓋前一頁的數(shù)據(jù)。
優(yōu)化四:如果數(shù)據(jù)當(dāng)中有圖片的話,使用第三方庫來加載(也就是緩存),如果你的能力強(qiáng)大到能自己維護(hù)的話,那也不是不可以。
優(yōu)化五:當(dāng)你手指在滑動(dòng)列表的時(shí)候,盡可能的不加載圖片,這樣的話滑動(dòng)就會(huì)更加流暢。
十四 動(dòng)畫
1.Android動(dòng)畫的分類
1.1 補(bǔ)間動(dòng)畫
a.漸變動(dòng)畫支持四種類型:平移(Translate)、旋轉(zhuǎn)(Rotate)、縮放(Scale)、不透明度
b. 只是顯示的位置變動(dòng),View的實(shí)際位置未改變,表現(xiàn)為View移動(dòng)到其他地方,點(diǎn)擊事件仍在原處才能響應(yīng)。
c. 組合使用步驟較復(fù)雜。
d. View Animation 也是指此動(dòng)畫。
1.2 幀動(dòng)畫
a. 用于生成連續(xù)的Gif效果圖。
b. DrawableAnimation也是指此動(dòng)畫
1.3 屬性動(dòng)畫
a.支持對(duì)所有View能更新的屬性的動(dòng)畫(需要屬性的setXxx()和getXxx())。
b. 更改的是View實(shí)際的屬性,所以不會(huì)影響其在動(dòng)畫執(zhí)行后所在位置的正常使用。
c. Android3.0(API11)及以后出現(xiàn)的功能,3.0之前的版本可使用github第三方開源庫nineoldandroids.jar進(jìn)行支持。
2.補(bǔ)間動(dòng)畫,幀動(dòng)畫,屬性動(dòng)畫優(yōu)缺點(diǎn)
2.1 補(bǔ)間動(dòng)畫優(yōu)缺點(diǎn)
缺點(diǎn):當(dāng)平移動(dòng)畫執(zhí)行完停在最后的位置,結(jié)果焦點(diǎn)還在原來的位置(控件的屬性沒有真的被改變)
優(yōu)點(diǎn):相對(duì)于逐幀動(dòng)畫來說,補(bǔ)間動(dòng)畫更為連貫自然
2.2 幀動(dòng)畫優(yōu)缺點(diǎn)
缺點(diǎn):效果單一,逐幀播放需要很多圖片,占用控件較大
優(yōu)點(diǎn):制作簡單
2.3 屬性動(dòng)畫優(yōu)缺點(diǎn)
缺點(diǎn):(3.0+API出現(xiàn))向下兼容問題。
優(yōu)點(diǎn):易定制,效果強(qiáng)。
十五 自定義View
1. 自定義View的幾種方式
對(duì)原View進(jìn)行擴(kuò)展方式
多個(gè)View的組合方式
重寫View的方式
2.自定義View需要重寫的方法。
- onMesure(測量)
- onLayout(布局)
- onDraw(繪制)
十六 Context
Context的中文翻譯為:語境; 上下文; 背景; 環(huán)境,在開發(fā)中我們經(jīng)常說稱之為“上下文”,那么這個(gè)“上下文”到底是指什么意思呢?在語文中,我們可以理解為語境,在程序中,我們可以理解為當(dāng)前對(duì)象在程序中所處的一個(gè)環(huán)境,一個(gè)與系統(tǒng)交互的過程。比如微信聊天,此時(shí)的“環(huán)境”是指聊天的界面以及相關(guān)的數(shù)據(jù)請(qǐng)求與傳輸,Context在加載資源、啟動(dòng)Activity、獲取系統(tǒng)服務(wù)、創(chuàng)建View等操作都要參與。
Context提供了關(guān)于應(yīng)用環(huán)境全局信息的接口。它是一個(gè)抽象類,它的執(zhí)行被Android系統(tǒng)所提供。它允許獲取以應(yīng)用為特征的資源和類型,是一個(gè)統(tǒng)領(lǐng)一些資源(應(yīng)用程序環(huán)境變量等)的上下文。就是說,它描述一個(gè)應(yīng)用程序環(huán)境的信息(即上下文);是一個(gè)抽象類,Android提供了該抽象類的具體實(shí)現(xiàn)類;通過它我們可以獲取應(yīng)用程序的資源和類(包括應(yīng)用級(jí)別操作,如啟動(dòng)Activity,發(fā)廣播,接受Intent等)。
Context的作用域:
十七 ContentProvider
1. 簡介
內(nèi)容提供者(Content Provider)主要用于在不同的應(yīng)用程序之間實(shí)現(xiàn)數(shù)據(jù)共享的功能,它提供了一套完整的機(jī)制,允許一個(gè)程序訪問另一個(gè)程序中的數(shù)據(jù),同時(shí)還能保證被訪數(shù)據(jù)的安全性。目前,使用內(nèi)容提供者是Android實(shí)現(xiàn)跨程序共享數(shù)據(jù)的標(biāo)準(zhǔn)方式。
不同于文件存儲(chǔ)和SharedPreferences存儲(chǔ)中的兩種全局可讀可寫操作模式,內(nèi)容提供者可以選擇只對(duì)哪一部分?jǐn)?shù)據(jù)進(jìn)行共享,從而保證我們程序中的隱私數(shù)據(jù)不會(huì)泄露的風(fēng)險(xiǎn)。
2.通過內(nèi)容提供者訪問其他程序的數(shù)據(jù)
內(nèi)容提供者的用法一般有兩種,一種是使用現(xiàn)有的內(nèi)容提供器來讀取和操作相應(yīng)程序中的數(shù)據(jù),另一種是創(chuàng)建自己的內(nèi)容提供者來給我們的程序提供外部訪問接口。如果一個(gè)程序通過內(nèi)容提供者對(duì)其數(shù)據(jù)提供外部訪問接口,那么任何其他的應(yīng)用程序就都可以對(duì)這部分?jǐn)?shù)據(jù)進(jìn)行訪問。Android系統(tǒng)中自帶的電話簿,短信,媒體庫等程序都提供了類似的訪問接口,這就使得第三方應(yīng)用程序可以充分利用這部分?jǐn)?shù)據(jù)來實(shí)現(xiàn)更好的功能。下面我們就來看看如何通過內(nèi)容提供者訪問其他程序的數(shù)據(jù):
2.1 ContentResolver的基本用法
想要訪問內(nèi)容提供者中共享的數(shù)據(jù),就一定要借助CotentResolver類,可以通過Context中的getContentResolver()方法獲取該類的實(shí)例。ContentResolver中提供了一系列的方法用于對(duì)數(shù)據(jù)進(jìn)行CRUD(增刪改查)操作,其中insert()方法用于添加數(shù)據(jù),update()方法用于數(shù)據(jù)的更新,delete()方法用于數(shù)據(jù)的刪除,query()方法用于數(shù)據(jù)的查詢。這好像SQLite數(shù)據(jù)庫操作有木有?
??不同于SQLiteDatabase,ContentResolver中的增刪改查方法都是不接收表名參數(shù)的,而是使用一個(gè)Uri的參數(shù)代替,這個(gè)參數(shù)被稱作內(nèi)容URI。內(nèi)容URI給內(nèi)容提供者中的數(shù)據(jù)建立了唯一的標(biāo)識(shí)符,它主要由兩部分組成:authority和path。authority是用于對(duì)不同的應(yīng)用程序做區(qū)分的,一般為了避免沖突,都會(huì)采用程序包名的方式來進(jìn)行命名。比如某個(gè)程序的包名為com.example.app,那么該程序?qū)?yīng)的authority就可以命名為com.example.app.provider。path則是用于對(duì)同一應(yīng)用程序中不同的表做區(qū)分的,通常都會(huì)添加到authority的后面。比如某個(gè)程序的數(shù)據(jù)庫里存在兩張表:table1和table2,這時(shí)就可以將path分別命名為/table1和/table2,然后把a(bǔ)uthority和path進(jìn)行組合,內(nèi)容的URI就變成了com.example.app.provider/table1和com.example.app.provider/table2。不過目前還是很難辨認(rèn)出這兩個(gè)字符串就是兩個(gè)內(nèi)容URI,我們還需要在字符串的頭部加上協(xié)議聲明。因此,內(nèi)容URI最標(biāo)準(zhǔn)的格式寫法如下:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
在得到內(nèi)容URI字符串之后,我們還需要將它解析成Uri對(duì)象才可以作為參數(shù)傳入。解析的方法也相當(dāng)簡單,代碼如下所示:
Uri uri = new Uri.parse("content://com.example.app.provider/table1");
只需要調(diào)用Uri的靜態(tài)方法parse()就可以把內(nèi)容URI字符串解析成URI對(duì)象。
??現(xiàn)在,我們可以通過這個(gè)Uri對(duì)象來查詢table1表中的數(shù)據(jù)了。代碼如下所示:
Cursor cursor = getContentResolver()
.query(
uri,projection,selection,selectionArgs,sortOrder
);
query()方法接收的參數(shù)跟SQLiteDatabase中的query()方法接收的參數(shù)很像,但總體來說這個(gè)稍微簡單一些,畢竟這是在訪問其他程序中的數(shù)據(jù),沒必要構(gòu)建復(fù)雜的查詢語句。下標(biāo)對(duì)內(nèi)容提供者中的query的接收的參數(shù)進(jìn)行了詳細(xì)的解釋:
查詢完成仍然會(huì)返回一個(gè)Cursor對(duì)象,這時(shí)我們就可以將數(shù)據(jù)從Cursor對(duì)象中逐個(gè)讀取出來了。讀取的思路仍然是對(duì)這個(gè)Cursor對(duì)象進(jìn)行遍歷,然后一條一條的取出數(shù)據(jù)即可,代碼如下:
if(cursor != null){//注意這里一定要進(jìn)行一次判空,因?yàn)橛锌赡苣阋樵兊谋砀静淮嬖? while(cursor.moveToNext()){
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
}
增加,刪除,修改
//增加數(shù)據(jù)
ContentValues values = new ContentValues();
values.put("Column1","text");
values.put("Column2","1");
getContextResolver.insert(uri,values);
//刪除數(shù)據(jù)
getContextResolver.delete(uri,"column2 = ?",new String[]{ "1" });
//更新數(shù)據(jù)
ContentValues values = new ContentValues();
values.put("Column1","改數(shù)據(jù)");
getContextResolver.update(uri,values,"column1 = ? and column2 = ?",new String[]{"text","1"});
3. 創(chuàng)建自己的內(nèi)容提供者
前面已經(jīng)提到過,如果要想實(shí)現(xiàn)跨程序共享數(shù)據(jù)的功能,官方推薦的方式就是使用內(nèi)容提供器,可以新建一個(gè)類去繼承ContentProvider類的方式來創(chuàng)建一個(gè)自己的內(nèi)容提供器。ContentProvider類有6個(gè)抽象方法,我們?cè)谑褂米宇惱^承它的時(shí)候,需要將這6個(gè)方法全部重寫。新建MyProvider繼承字ContentProvider類,代碼如下所示:
public class MyProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}//查詢
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}//添加
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}//更新
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}//刪除
@Override
public String getType(Uri uri) {
return null;
}
}
onCreate()方法:
??初始化內(nèi)容提供器的時(shí)候調(diào)用。通常會(huì)在這里完成對(duì)數(shù)據(jù)庫的創(chuàng)建和升級(jí)等操作。返回true表示內(nèi)容提供器初始化成功,返回false則表示失敗。注意,只有當(dāng)存在ContentResolver嘗試訪問我們的程序中的數(shù)據(jù)時(shí),內(nèi)容提供器才會(huì)被初始化。query()方法:
??從內(nèi)容提供器中查詢數(shù)據(jù)。使用uri參數(shù)來確定查詢的哪張表,projection參數(shù)用于確定查詢的哪一列,selection和selectionArgs參數(shù)用于約束查詢哪些行,sortOrder參數(shù)用于對(duì)結(jié)果進(jìn)行排序,查詢的結(jié)果存放在Cursor對(duì)象中返回。insert()方法:
??向內(nèi)容提供器中添加一條數(shù)據(jù)。使用uri參數(shù)來確定要添加的表,待添加的數(shù)據(jù)保存在values參數(shù)中。添加完成后,返回一個(gè)用于表示這條新紀(jì)錄的URI。update()方法:
??更新內(nèi)容提供器中已有的數(shù)據(jù)。使用uri參數(shù)來確定更新哪一張表中的數(shù)據(jù),新數(shù)據(jù)保存著values參數(shù)當(dāng)中,selection和selectionArgs參數(shù)用于約束更新哪些行,受影響的行數(shù)將作為返回值返回。delete()方法:
??從內(nèi)容提供器中刪除數(shù)據(jù)。使用uri參數(shù)來確定刪除哪一張表中的數(shù)據(jù),selection和selectionArgs參數(shù)用于約束刪除哪些行,被刪除的行數(shù)將作為返回值返回。getType()方法:
??根據(jù)傳入的內(nèi)容URI來返回相應(yīng)的MIME類型。