Android知識點總結(jié)

一 Activity

1 Activity 生命周期

1.1 Activity 的四種狀態(tài)

running 當(dāng)前Activity正在運行,獲取焦點
paused 當(dāng)前Activity處于暫停狀態(tài),可見,沒有焦點
stopped 當(dāng)前Activity處于暫停狀態(tài),完全不可見,內(nèi)存里的成員變量和狀態(tài)信息仍在。
killed 當(dāng)前Activity被銷毀后的狀態(tài),成員變量和狀態(tài)信息被一并回收。

1.2 Activity的生命周期

Activity啟動 →onCreate()→onStart()→onResume();
點擊home鍵返回桌面→onPause()→onStop();
再次回到原Activity→ onRestart()→onStart()→onResume();
按返回鍵退出當(dāng)前Activity→onPause()→onStop()→onDestroy();

2 Android任務(wù)棧

優(yōu)先級:前臺>可見>服務(wù)>后臺>空
前臺:正在與用戶進行交互的Activity所在的進程
可見:Activity可見但沒有在前臺所在的進程
服務(wù):Activity在后臺開啟了服務(wù)所在的進程
后臺:Activity完全處于后臺所在的進程
空:沒有任何Activity存在的進程

3. Activity的啟動模式

3.1 為什么需要啟動模式?

? ? ? ?每次啟動一個Activity都會把對應(yīng)的要啟動的Activity的實例放入任務(wù)棧中,加入這個Activity被頻繁啟動,會產(chǎn)生很多的這個Activity的實例,為了杜絕這種內(nèi)存浪費的行為,Activity的啟動模式被創(chuàng)造出來。

3.2 Activity的啟動模式

  • 系統(tǒng)模式模式:standard
    ? ? ? ?標(biāo)準(zhǔn)模式,也是系統(tǒng)的默認模式,啟動一個activity就創(chuàng)建一個activity實例,不管這個實例是否存在,誰啟動了這個Activity,那么這個Activity就運行在啟動它的那個Activity的任務(wù)棧中。
  • 棧頂復(fù)用模式:singleTop
    ? ? ? ?在這種模式下,如果新的Activity已經(jīng)位于棧頂,那么此Activity不會被重新創(chuàng)建,同時它的onNewIntent方法被回調(diào),通過此方法的參數(shù)我們可以取出當(dāng)前的請求信息。需要注意,此Activity的onCreate,onStart方法不會被系統(tǒng)調(diào)用。如果新Activity不在棧頂,那么新Activity任然會被重新重建。
  • 棧內(nèi)復(fù)用模式:singleTask
    ? ? ? ?這是一種單實例模式,只要Activity在一個棧中存在,那么多次啟動此Activity都不會重新創(chuàng)建實例,系統(tǒng)也會回調(diào)onNewIntent方法。
    例如:當(dāng)前棧內(nèi)情況為ABC,此時D被以singleTask的模式被啟動,當(dāng)前棧變?yōu)锳BCD。
    如果當(dāng)前棧內(nèi)情況為ADBC,此時D被以singleTask的模式被啟動,當(dāng)前棧變?yōu)锳D。
  • 單實例模式:singleInstance
    ? ? ? ?這是一種加強的單實例模式,它除了具有singleTask模式的所有特性外,還加強了一點,那就是具有此種模式的Activity只能單獨位于一個任務(wù)棧中,比如Activity A是singleInstance模式,A被啟動時系統(tǒng)會為它創(chuàng)建一個新的任務(wù)棧,A運行在這個單獨的任務(wù)棧中,后續(xù)的請求均不會再創(chuàng)建A,除非這個單獨的任務(wù)棧被系統(tǒng)銷毀了。

二 Fragment

1. 為什么Fragment被稱為第五大組件?

Android中的四大組件為Activity,service,ContentProvider,Broadcast。
Fragment因為有生命周期,使用頻率不輸于四大組件,可靈活加載到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"/> 
  • 動態(tài)加載:動態(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的使用方式,這就涉及到兩個常用的適配器的使用,一個是FragmentPagerAdapter,另外一個是FragmentStatePagerAdapter,那么它們之間有什么區(qū)別呢?其實很簡單,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,對應(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有個getActivity()的方法,比如,在MainActivity中的一個Fragment中獲取MainActivity的引用,并調(diào)用MainActivity的某個方法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)用另外一個Fragment的方法

? ? ? ?這個可就需要一定的思維性了,首先要想調(diào)用Fragment A的方法,除了這個Fragment A自身可以調(diào)用外,這個Fragment A所屬的Activity也可以調(diào)用,要想另外一個Fragment B調(diào)用此Fragment A的方法,F(xiàn)ragment B可以間接通過Activity來進行調(diào)用,也就是3.1 和 3.2 的結(jié)合。

三 Service

1. Service基礎(chǔ)知識

1.1 Service是什么?

? ? ? ?Service(服務(wù))是一個一種可以在后臺執(zhí)行長時間運行操作而沒有用戶界面的組件。它運行于UI線程,因此不能進行耗時的操作。

1.2 Service和Thread的區(qū)別

? ? ? ?Service的運行是在UI線程當(dāng)中的,是絕對絕對不能進行耗時操作的,而Thread開啟的子線程則可以進行耗時操作,但是Thread開啟的子線程是不能直接對UI進行操作的,否則極有可能發(fā)生直接讓程序崩掉,這就是它們的區(qū)別。

2. 啟動Service的2種方式

2.1 startService()方法開啟Service

步驟:
??a.定義一個類繼承Service。
??b.在AndroidManifest.xml文件中配置該Service。
??c.使用Context的startService(Intent)方法啟動該Service。
??d.不再使用該Service時,調(diào)用Context的stopService(Intent)方法停止該Service。

2.2 bindService方法開啟Service(Activity與Service綁定)

步驟:
??a.創(chuàng)建BinderService服務(wù)端,繼承自Service并在類中創(chuàng)建一個實現(xiàn)IBinder接口的實現(xiàn)實例對象并提供公共方法給客戶端調(diào)用。
??b.從onBind()回調(diào)方法返回此Binder實例。
??c.在客戶端中,從onServiceConnected回調(diào)方法接收Binder,并使用提供的方法調(diào)用綁定服務(wù)。

3. Service的生命周期

? ? ? ?服務(wù)的生命周期有兩種,因為服務(wù)可以跟Activity綁定起來,也可以不綁定,Activity和服務(wù)進行通信的話,是需要把服務(wù)和Activity進行綁定的。因此服務(wù)的生命周期分為未綁定Activity的和綁定Activity的。

沒有綁定Activity的服務(wù)生命周期:

啟動服務(wù)>onCreate()>onStartCommand()>服務(wù)運行>onDestory()>服務(wù)銷毀

綁定Activity的服務(wù)生命周期

綁定服務(wù)>onCreate()>onBind()>服務(wù)運行>onUnBind()>onDestory()>服務(wù)被銷毀

  1. 通過Intent和startService()方法啟動了一個服務(wù),接下來執(zhí)行onCreate()方法,首次創(chuàng)建服務(wù)時,系統(tǒng)將調(diào)用此方法來執(zhí)行一次性設(shè)置程序(在調(diào)用 onStartCommand() 或 onBind() 之前)。如果服務(wù)已在運行,則不會調(diào)用此方法。

  2. 當(dāng)另一個組件(如 Activity)通過調(diào)用 startService() 請求啟動服務(wù)時,系統(tǒng)將調(diào)用此方法。一旦執(zhí)行此方法,服務(wù)即會啟動并可在后臺無限期運行。 如果您實現(xiàn)此方法,則在服務(wù)工作完成后,需要由您通過調(diào)用 stopSelf() 或 stopService() 來停止服務(wù)。(如果您只想提供綁定,則無需實現(xiàn)此方法。)

  3. 服務(wù)開始處于運行狀態(tài)。

  4. 某個操作導(dǎo)致服務(wù)停止,比如執(zhí)行了方法stopService(),那么服務(wù)接下來會執(zhí)行onDestory()銷毀。服務(wù)應(yīng)該實現(xiàn)此方法來清理所有資源,如線程、注冊的偵聽器、接收器等。 這是服務(wù)接收的最后一個調(diào)用。

  5. 服務(wù)被完全銷毀,下一步就是等待被垃圾回收器回收了。

  6. 通過Intent和bindService()方法啟動了一個服務(wù),接下來會執(zhí)行onCreate()方法,首次創(chuàng)建服務(wù)時,系統(tǒng)將調(diào)用此方法來執(zhí)行一次性設(shè)置程序(在調(diào)用 onStartCommand() 或 onBind() 之前)。如果服務(wù)已在運行,則不會調(diào)用此方法。

  7. 當(dāng)另一個組件想通過調(diào)用 bindService() 與服務(wù)綁定(例如執(zhí)行 RPC)時,系統(tǒng)將調(diào)用此方法。在此方法的實現(xiàn)中,您必須通過返回 IBinder 提供一個接口,供客戶端用來與服務(wù)進行通信。請務(wù)必實現(xiàn)此方法,但如果您并不希望允許綁定,則應(yīng)返回 null。

  8. 服務(wù)開始處于運行狀態(tài)。成功與Activity綁定。

  9. 某個操作導(dǎo)致服務(wù)解除綁定,比如執(zhí)行了方法unbindService(),那么服務(wù)接下來會解除與當(dāng)前Activity的綁定。接下來服務(wù)將面臨銷毀。

  10. 服務(wù)執(zhí)行onDestory()方法被銷毀。服務(wù)應(yīng)該實現(xiàn)此方法來清理所有資源,如線程、注冊的偵聽器、接收器等。 這是服務(wù)接收的最后一個調(diào)用。

  11. 服務(wù)被完全銷毀,下一步就是等待被垃圾回收器回收了。

Service總結(jié):

  1. 被啟動的服務(wù)的生命周期:如果一個Service被某個Activity 調(diào)用 Context.startService 方法啟動,那么不管是否有Activity使用bindService綁定或unbindService解除綁定到該Service,該Service都在后臺運行。如果一個Service被startService 方法多次啟動,那么onCreate方法只會調(diào)用一次,onStart將會被調(diào)用多次(對應(yīng)調(diào)用startService的次數(shù)),并且系統(tǒng)只會創(chuàng)建Service的一個實例(因此你應(yīng)該知道只需要一次stopService調(diào)用)。該Service將會一直在后臺運行,而不管對應(yīng)程序的Activity是否在運行,直到被調(diào)用stopService,或自身的stopSelf方法。當(dāng)然如果系統(tǒng)資源不足,android系統(tǒng)也可能結(jié)束服務(wù)。

  2. 被綁定的服務(wù)的生命周期:如果一個Service被某個Activity 調(diào)用 Context.bindService 方法綁定啟動,不管調(diào)用 bindService 調(diào)用幾次,onCreate方法都只會調(diào)用一次,同時onStart方法始終不會被調(diào)用。當(dāng)連接建立之后,Service將會一直運行,除非調(diào)用Context.unbindService 斷開連接或者之前調(diào)用bindService 的 Context 不存在了(如Activity被finish的時候),系統(tǒng)將會自動停止Service,對應(yīng)onDestroy將被調(diào)用。

  3. 被啟動又被綁定的服務(wù)的生命周期:如果一個Service又被啟動又被綁定,則該Service將會一直在后臺運行。并且不管如何調(diào)用,onCreate始終只會調(diào)用一次,對應(yīng)startService調(diào)用多少次,Service的onStart便會調(diào)用多少次。調(diào)用unbindService將不會停止Service,而必須調(diào)用 stopService 或 Service的 stopSelf 來停止服務(wù)。

  4. 當(dāng)服務(wù)被停止時清除服務(wù):當(dāng)一個Service被終止(1、調(diào)用stopService;2、調(diào)用stopSelf;3、不再有綁定的連接(沒有被啟動))時,onDestroy方法將會被調(diào)用,在這里你應(yīng)當(dāng)做一些清除工作,如停止在Service中創(chuàng)建并運行的線程。

四 Broadcast

1. 廣播的概念

1.1 定義

在Android中,它是一種廣泛運用在應(yīng)用程序之間傳輸信息的機制,Android中我們發(fā)送廣播內(nèi)容是一個Intent,這個Intent中可以攜帶我們要發(fā)送的數(shù)據(jù)。

1.2 廣播的使用場景

a.同一app內(nèi)有多個進程的不同組件之間的消息通信。

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)注冊和動態(tài)注冊:

  • 靜態(tài)注冊:注冊完成一直在運行。

首先你要創(chuàng)建一個廣播接收器類,實例代碼如下:

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文件中注冊才可以使用,AndroidManifest.xml文件中注冊靜態(tài)廣播代碼如下:

          <receiver 
               android:name=".BootCompleteReceiver" >
               <intent-filter>
                   <action android:name="android.intent.action.BOOT_COMPLETED" />
               </intent-filter>
           </receiver>
  • 動態(tài)注冊:跟隨Activity的生命周期。
    新建一個類,讓它繼承自BroadcastReceiver,并重寫父類的onReceive()方法就行了。這樣有廣播到來時,onReceive()方法就會得到執(zhí)行,具體的邏輯就可以在這個方法中處理。
  • 動態(tài)注冊廣播接收器的優(yōu)點以及缺點:
    動態(tài)注冊的廣播接收器可以自由地控制注冊與注銷,在靈活性方面有很大優(yōu)勢,但是它也存在著一個缺點,即必須要在程序啟動之后才能接收到廣播,因為注冊的邏輯是寫在onCreate()方法中的。那么有沒有廣播能在程序未啟動的情況下就能接收到廣播呢?靜態(tài)注冊的廣播接收器就可以做到。

3. 廣播內(nèi)部實現(xiàn)機制

  1. 自定義廣播接收者BroadcastReceiver,并且重寫onReceiver()方法。

  2. 通過Binder機制向AMS(Activity Manager Service)進行注冊。

  3. 廣播發(fā)送者通過Binder機制向AMS發(fā)送廣播。

  4. AMS查找符合條件(IntentFilter/Permission等)的BroadcastReceiver,將廣播發(fā)送到相應(yīng)的BroadcastReceiver(一般情況下是Activity)的消息隊列中。

  5. 消息循環(huán)執(zhí)行拿到此廣播,回調(diào)BroadcastReceiver中的onReceiver()方法。

4. 本地廣播

本地廣播的發(fā)送和注冊廣播接收器都需要使用到LocalBroadcastManager類,如下所示為本地廣播的發(fā)送和本地廣播接收器注冊的代碼:
??本地廣播的發(fā)送:

public static void sendLocalBroadcast(Context context,String action){

    Intent intent = new Intent(action);
    LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
    localBroadcastManager.sendBroadcast(intent);

}

本地廣播的接收器的注冊:

IntentFilter intentFilter = new IntentFilter();
    LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);

    intentFilter.addAction(new BroadcastUtil().action_next);
    nasbr = new NextAndStartBroadcastReceiver();
    localBroadcastManager.registerReceiver(nasbr, intentFilter);//注冊本地廣播接收器

特點:

1. 使用它發(fā)送的廣播將只在自身app內(nèi)傳播,因此你不必擔(dān)心泄漏隱私的數(shù)據(jù)。

2. 其他app無法對你的app發(fā)送該廣播,因此你的app根本不可能收到非自身app發(fā)送的該廣播,因此你不必擔(dān)心有安全漏洞可以利用。

3. 比系統(tǒng)廣播更加高效。

內(nèi)部實現(xiàn)機制:

1. LocalBroadcast高效的原因:因為它內(nèi)部是通過Handler實現(xiàn)的,它的sendBroadcast()方法含義并非和系統(tǒng)的sendBroadcast()一樣,它的sendBroadcast()方法其實就是通過Handler發(fā)送了一個Message而已。

2. LocalBroadcast安全的原因:既然它是通過Handler實現(xiàn)廣播發(fā)送的,那么相比系統(tǒng)廣播通過Binder機制實現(xiàn)那肯定更加高效,同時使用Handler來實現(xiàn),別的app無法向我們應(yīng)用發(fā)送該廣播,而我們app內(nèi)部發(fā)送的廣播也不會離開我們的app。

LocalBroadcast內(nèi)部協(xié)作主要是靠兩個Map集合:mReceivers和mActions,當(dāng)然還有一個List集合mPendingBroadcasts,這個主要存儲待接收的廣播對象。

五 WebView

1. WebView常見的坑

  1. API 16之前版本存在遠程代碼執(zhí)行漏洞,該漏洞源自于程序沒有正確限制使用WebView.addJavascriptInterface方法,攻擊者可以使用Java Reflection API利用該漏洞執(zhí)行任意Java對象和方法。

  2. WebView的銷毀和內(nèi)存泄漏問題。WebView的完全銷毀是件麻煩事,一旦銷毀流程不正確,極易容易導(dǎo)致內(nèi)存泄漏。

  3. jsbridge
    ??通過javascript構(gòu)建一個橋,橋的兩端一個端是客戶端,一端是服務(wù)端,它可以讓本地端調(diào)用遠端的web代碼,也可以讓遠端調(diào)用本地的代碼。

  4. WebViewClient.onPageFinished(不靠譜) –> WebChromeClient.onProgressChanged(靠譜)

  5. 后臺耗電(性能優(yōu)化)
    ??WebView開啟網(wǎng)頁時會自己開啟線程,如果沒有合理的銷毀,那么殘余線程就會一直運行,so這會非常耗電的,解決方案:有一種暴力方式就是Activity的onDestroy中調(diào)用System.exit()方法把虛擬機關(guān)閉,也可以結(jié)合自己應(yīng)用的WebView的情況設(shè)計出一個溫柔的方案。

  6. WebView硬件加速導(dǎo)致的頁面渲染問題
    ??WebView硬件加速偶爾導(dǎo)致頁面白塊,頁面閃爍,但是加載速度比較快,解決方案:關(guān)閉硬件加速。

2. WebView的內(nèi)存泄漏問題

原因:WebView會關(guān)聯(lián)一個Activity,WebView執(zhí)行的操作是在新線程當(dāng)中回收,時間Activity沒有辦法確認,Activity的生命周期和WebView線程生命周期不一致導(dǎo)致WebView一直執(zhí)行,因為WebView內(nèi)部持有Activity的引用,導(dǎo)致Activity一直不能被回收,原理類似于匿名內(nèi)部類持有外部類的引用一樣。那么如何解決呢?解決方案如下:

  1. 獨立進程,簡單暴力,涉及到進程間通信。(開發(fā)過程中常用)

  2. 動態(tài)添加WebView,對傳入WebView中使用Context弱引用,動態(tài)添加WebView意思在布局中創(chuàng)建一個ViewGroup用來放置WebView,Activity創(chuàng)建add進來,Activity停止時remove掉。

六 Bainder機制

  1. 通常情況下,Binder是一種通信機制。

  2. 對于Server來說,Binder指的是Binder本地對象/對于Client來說,Binder指的是Binder的代理對象。

  3. 對于傳輸過程而言,Binder是可以進行跨進程傳遞的對象。

  4. AIDL是Binder機制的一個實例。

七 Handler機制

1. 定義

Handler是可以通過發(fā)送和處理Message和Runnable對象來關(guān)聯(lián)相應(yīng)線程的MessageQueue。通常我們認為它是一種異步機制。

  1. 可以讓對應(yīng)的Message和Runnable在未來的某個時間點進行相應(yīng)的處理。
  2. 讓自己想要的耗時操作在子線程中完成,讓更新UI的操作在主線程中完成,而子線程與主線程之間的通信就是靠Handler來完成。

2. Handler的使用方法

  • post(Runnable)
  • sendMessage(Message)

3. Handler內(nèi)部實現(xiàn)機制

Handler機制也可叫異步消息機制,它主要由4個部分組成:Message,Handler,MessageQueue,Looper,在上面我們已經(jīng)接觸到了Message和Handler,接下來我們對4個成員進行著重的了解:

  1. Message
    ??Message是在線程之間傳遞的消息,它可以在內(nèi)部攜帶少量的信息,用于在不同線程之間交換數(shù)據(jù)。使用Message的arg1和arg2便可攜帶int數(shù)據(jù),使用obj便可攜帶Object類型數(shù)據(jù)。

  2. Handler
    ??Handler顧名思義就是處理者的意思,它只要用于在子線程發(fā)送消息對象Message,在UI線程處理消息對象Message,在子線程調(diào)用sendMessage方法發(fā)送消息對象Message,而發(fā)送的消息經(jīng)過一系列地輾轉(zhuǎn)之后最終會被傳遞到Handler的handleMessage方法中,最終在handleMessage方法中消息對象Message被處理。

  3. MessageQueue
    ??MessageQueue就是消息隊列的意思,它只要用于存放所有通過Handler發(fā)送過來的消息。這部分消息會一直存放于消息隊列當(dāng)中,等待被處理。每個線程中只會有一個MessageQueue對象,請牢記這句話。其實從字面上就可以看出,MessageQueue底層數(shù)據(jù)結(jié)構(gòu)是隊列,而且這個隊列只存放Message對象。

  4. Looper
    ??Looper是每個線程中的MessageQueue的管家,調(diào)用Looper的loop()方法后,就會進入到一個無限循環(huán)當(dāng)中,然后每當(dāng)MesssageQueue中存在一條消息,Looper就會將這條消息取出,并將它傳遞到Handler的handleMessage()方法中。每個線程只有一個Looper對象。

Handler機制流程圖如下:


Handler機制流程圖

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機制

1.IntentService是什么?

它的優(yōu)先級高于Service。
??IntentService是繼承處理異步請求的一個類,在IntentService內(nèi)有一個工作線程來處理耗時操作,啟動IntentServiced的方式和啟動傳統(tǒng)的Service一樣,同時,當(dāng)任務(wù)執(zhí)行完成后,IntentService會自動停止,而不需要我們手動去控制或stopSelf()。另外,可以啟動IntentService多次,而每一個耗時操作會以工作隊列的方式在IntentService的onHandlerIntent回調(diào)方法中執(zhí)行,并且,每次只執(zhí)行一個工作線程,執(zhí)行完第一個在執(zhí)行第二個。

  1. 它本質(zhì)是一種特殊的Service,繼承自Service并且本身就是一個抽象類。
  2. 它內(nèi)部是由HandlerThread和Handler實現(xiàn)異步操作。

2.IntentService的使用方法

創(chuàng)建IntentService時,只需要實現(xiàn)onHandlerIntent和構(gòu)造方法,onHandlerIntent為異步方法,可以執(zhí)行耗時操作。

九 AsyncTask 機制

1.AsyncTask是什么?

它本質(zhì)上是一個封裝了線程池和Handler的異步框架。

2.AsyncTask的基本用法

  1. 3個參數(shù):Params, Progress 和 Result
  • Params表示用于AsyncTask執(zhí)行任務(wù)的參數(shù)的類型
  • Progress表示在后臺線程處理的過程中,可以階段性地發(fā)布結(jié)果的數(shù)據(jù)類型
  • Result表示任務(wù)全部完成后所返回的數(shù)據(jù)類型
  1. 5個方法
  • onPreExecute
    該方法是運行在主線程中的。在AsyncTask執(zhí)行了execute()方法后就會在UI線程上執(zhí)行onPreExecute()方法,該方法在task真正執(zhí)行前運行,我們通常可以在該方法中顯示一個進度條,從而告知用戶后臺任務(wù)即將開始。
  • doInBackground
    該方法是運行在單獨的工作線程中的,而不是運行在主線程中。
  • onProgressUpdate
    當(dāng)我們在doInBackground中調(diào)用publishProgress(Progress…)方法后,就會在UI線程上回調(diào)onProgressUpdate方法。
  • onPostExecute
    當(dāng)doInBackgroud方法執(zhí)行完畢后,就表示任務(wù)完成了,doInBackgroud方法的返回值就會作為參數(shù)在主線程中傳入到onPostExecute方法中,這樣就可以在主線程中根據(jù)任務(wù)的執(zhí)行結(jié)果更新UI。

3.AsyncTask機制的原理

  1. 它本質(zhì)上是一個靜態(tài)的線程池,AsyncTask派生出的子類可以實現(xiàn)不同的異步任務(wù),這些任務(wù)都是提交到靜態(tài)的線程池中執(zhí)行。

  2. 線程池中的工作線程執(zhí)行doInBackground(mParams)方法執(zhí)行異步的任務(wù)。

  3. 當(dāng)任務(wù)狀態(tài)改變后,工作線程向UI線程發(fā)送消息,AsyncTask內(nèi)部的InternalHandler響應(yīng)這些消息,并調(diào)用相關(guān)的回調(diào)函數(shù)。

4.AsyncTask注意事項:

  1. 內(nèi)存泄漏:

靜態(tài)內(nèi)部類持有外部類的匿名引用,導(dǎo)致外部對象無法得到釋放,解決方法很簡單,讓內(nèi)部持有外部的弱引用即可解決

  1. 生命周期
    ??在Activity的onDestory()中及時對AsyncTask進行回收,調(diào)用其cancel()方法來保證程序的穩(wěn)定性。

  2. 結(jié)果丟失
    ??當(dāng)內(nèi)存不足時,當(dāng)前的Activity被回收,由于AsyncTask持有的是回收之前Activity的引用,導(dǎo)致AsyncTask更新的結(jié)果對象為一個無效的Activity的引用,這就是結(jié)果丟失。

  3. 并行或串行
    ??在1.6(Donut)之前: 在第一版的AsyncTask,任務(wù)是串行調(diào)度。一個任務(wù)執(zhí)行完成另一個才能執(zhí)行。由于串行執(zhí)行任務(wù),使用多個AsyncTask可能會帶來有些問題。所以這并不是一個很好的處理異步(尤其是需要將結(jié)果作用于UI試圖)操作的方法。1.6-2.3: 所有的任務(wù)并發(fā)執(zhí)行,這會導(dǎo)致一種情況,就是其中一條任務(wù)執(zhí)行出問題了,會引起其他任務(wù)出現(xiàn)錯誤。3.0之后AsyncTask又修改為了順序執(zhí)行,并且新添加了一個函數(shù) executeOnExecutor(Executor),如果您需要并行執(zhí)行,則只需要調(diào)用該函數(shù),并把參數(shù)設(shè)置為并行執(zhí)行即可。

十 HandlerThread機制

1.HandlerThread的產(chǎn)生背景

開啟子線程進行耗時操作,多次創(chuàng)建和銷毀子線程是很耗費資源的,但是木有關(guān)系,谷歌考慮了這點為我們專門開發(fā)出了HandlerThread機制。

2.HandlerThread是什么?

本質(zhì):Handler + Thread + Looper,是一個Thread內(nèi)部有Looper。

  1. HandlerThread本質(zhì)上是一個線程類,它繼承了Thread。

  2. HandlerThread有自己內(nèi)部的Looper對象,可以進行Looper循環(huán)。

  3. 通過獲取HandlerThread的Looper對象傳遞給Handler對象,可以在handlerMessage方法中執(zhí)行異步任務(wù)。

  4. 優(yōu)點是不會有堵塞,減少對性能的消耗,缺點是不能進行多任務(wù)的處理,需要等待進行處理,處理效率較低。

  5. 與線程池注重并發(fā)不同,HandlerThread是一個串行隊列,HandlerThread背后只有一個線程。

十一 IntentService機制

1.IntentService是什么?

它的優(yōu)先級高于Service。
??IntentService是繼承處理異步請求的一個類,在IntentService內(nèi)有一個工作線程來處理耗時操作,啟動IntentServiced的方式和啟動傳統(tǒng)的Service一樣,同時,當(dāng)任務(wù)執(zhí)行完成后,IntentService會自動停止,而不需要我們手動去控制或stopSelf()。另外,可以啟動IntentService多次,而每一個耗時操作會以工作隊列的方式在IntentService的onHandlerIntent回調(diào)方法中執(zhí)行,并且,每次只執(zhí)行一個工作線程,執(zhí)行完第一個在執(zhí)行第二個。

  • 它本質(zhì)是一種特殊的Service,繼承自Service并且本身就是一個抽象類。

  • 它內(nèi)部是由HandlerThread和Handler實現(xiàn)異步操作。

2.IntentService的使用方法

創(chuàng)建IntentService時,只需要實現(xiàn)onHandlerIntent和構(gòu)造方法,onHandlerIntent為異步方法,可以執(zhí)行耗時操作。

十二 View繪制機制

1. View樹的繪制流程

measure(測量)→layout(布局)→draw(繪制)

2. Measure過程

Measure過程

measure過程主要就是從頂層父View向子View遞歸調(diào)用view.measure方法(measure中又回調(diào)onMeasure方法)的過程。具體measure核心主要有如下幾點:

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è)計值來決定;

View的measure方法是final的,不允許重載,View子類只能重載onMeasure來完成自己的測量邏輯。

最頂層DecorView測量時的MeasureSpec是由ViewRootImpl中g(shù)etRootMeasureSpec方法確定的(LayoutParams寬高參數(shù)均為MATCH_PARENT,specMode是EXACTLY,specSize為物理屏幕大小)。

ViewGroup類提供了measureChild,measureChild和measureChildWithMargins方法,簡化了父子View的尺寸計算。

只要是ViewGroup的子類就必須要求LayoutParams繼承子MarginLayoutParams,否則無法使用layout_margin參數(shù)。

View的布局大小由父View和子View共同決定。

使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程之后被調(diào)用才能返回有效值。

3. Layout過程

整個layout過程比較容易理解,從上面分析可以看出layout也是從頂層父View向子View的遞歸調(diào)用view.layout方法的過程,即父View根據(jù)上一步measure子View所得到的布局大小和布局參數(shù),將子View放在合適的位置上。具體layout核心主要有以下幾點:

  • View.layout方法可被重載,ViewGroup.layout為final的不可重載,ViewGroup.onLayout為abstract的,子類必須重載實現(xiàn)自己的位置邏輯。

  • measure操作完成后得到的是對每個View經(jīng)測量過的measuredWidth和measuredHeight,layout操作完成之后得到的是對每個View進行位置分配后的mLeft、mTop、mRight、mBottom,這些值都是相對于父View來說的。

  • 凡是layout_XXX的布局屬性基本都針對的是包含子View的ViewGroup的,當(dāng)對一個沒有父容器的View設(shè)置相關(guān)layout_XXX屬性是沒有任何意義的(前面《Android應(yīng)用setContentView與LayoutInflater加載解析機制源碼分析》也有提到過)。

  • 使用View的getWidth()和getHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onLayout流程之后被調(diào)用才能返回有效值。

4.Draw過程

繪制過程就是把View對象繪制到屏幕上,整個draw過程需要注意如下細節(jié):

  • 如果該View是一個ViewGroup,則需要遞歸繪制其所包含的所有子View。

  • View默認不會繪制任何內(nèi)容,真正的繪制都需要自己在子類中實現(xiàn)。

  • View的繪制是借助onDraw方法傳入的Canvas類來進行的。

  • 區(qū)分View動畫和ViewGroup布局動畫,前者指的是View自身的動畫,可以通過setAnimation添加,后者是專門針對ViewGroup顯示內(nèi)部子視圖時設(shè)置的動畫,可以在xml布局文件中對ViewGroup設(shè)置layoutAnimation屬性(譬如對LinearLayout設(shè)置子View在顯示時出現(xiàn)逐行、隨機、下等顯示等不同動畫效果)。

  • 在獲取畫布剪切區(qū)(每個View的draw中傳入的Canvas)時會自動處理掉padding,子View獲取Canvas不用關(guān)注這些邏輯,只用關(guān)心如何繪制即可。

  • 默認情況下子View的ViewGroup.drawChild繪制順序和子View被添加的順序一致,但是你也可以重載ViewGroup.getChildDrawingOrder()方法提供不同順序。

十三 Android部分事件分發(fā)機制

1. 為什么有事件分發(fā)機制

Android上面的View是樹形結(jié)構(gòu),View可能會重疊在一起,當(dāng)我們點擊的地方有多個View都可以響應(yīng)的時候,這個點擊事件應(yīng)該給誰呢?為了解決這個問題,就有了事件分發(fā)機制。

2. 3個重要的有關(guān)事件分發(fā)的方法

  • dispatch TouchEvent
    用來進行事件的分發(fā)。如果事件能夠傳遞給當(dāng)前View,那么此方法一定會被調(diào)用,返回結(jié)果受當(dāng)前View的onTouchEvent和下級的dispatchTouchEvent方法影響,表示是否消耗此事件。
  • onInterceptTouchEvent
    在上述方法dispatchTouchEvent內(nèi)部調(diào)用,用來判斷是否攔截某個事件,如果當(dāng)前View攔截了某個事件,那么同一個事件序列當(dāng)中,此方法不會被再次調(diào)用,返回結(jié)果表示是否攔截當(dāng)前事件。
  • onTouchEvent
    同樣也會在dispatchTouchEvent內(nèi)部調(diào)用,用來處理點擊事件,返回結(jié)果表示是否消耗當(dāng)前事件,如果不消耗,則在同一個事件序列中,當(dāng)前View無法再次接收到事件。
    偽代碼
public boolean dispatchTouchEvent(MotionEvent ev){

    boolean consume = false;//記錄返回值

    if(onInterceptTouchEvent(ev)){//判斷是否攔截此事件

        consume = onTouchEvent(ev);//如果當(dāng)前確認攔截此事件,那么就處理這個事件 

    }else{

        consume = child.dispatchToucnEvent(ev);//如果當(dāng)前確認不攔截此事件,那么就將事件分發(fā)給下一級

    }

    return consume;

}

通過上述偽代碼,我們可以得知點擊事件的傳遞規(guī)則:對于一個根ViewGroup而言,點擊事件產(chǎn)生后,首先會傳遞給它,這時它的dispatchTouch就會被調(diào)用,如果這個ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當(dāng)前的事件,接著事件就會交給這個ViewGroup處理,即它的onTouch方法就會被調(diào)用;如果這個ViewGroup的onInterceptTouchEvent方法返回false就表示它不攔截當(dāng)前事件,這時當(dāng)前事件就會繼續(xù)傳遞給它的子元素,接著子元素的dispatchTouchEvent方法就會被調(diào)用,如此直到事件被最終處理。

當(dāng)一個View需要處理事件時,如果它設(shè)置了OnTouchListener,那么OnTouchListener中的onTouch方法會被回調(diào)。這時事件處理還要看onTouch的返回值,如果返回false,則當(dāng)前View的onTouchEvent方法會被調(diào)用;如果返回true,那么當(dāng)前View的onTouchEvent方法不會被調(diào)用。由此可見,給View設(shè)置的onTouchListener的優(yōu)先級比onTouchEvent要高。在onTouchEvent方法中,如果當(dāng)前設(shè)置的有onClickListener,那么它的onClick方法會被調(diào)用。可以看出,平時我們常用的OnClickListener,其優(yōu)先級最低,即處于事件傳遞的尾端。

當(dāng)一個點擊事件產(chǎn)生后,它的傳遞過程遵循如下順序:Activity–>Window–>View,即事件總數(shù)先傳遞給Activity,Activity再傳遞給Window,最后Window再傳遞給頂級View,頂級View接收到事件后,就會按照事件分發(fā)機制去分發(fā)事件。考慮一種情況,如果一個View的onTouchEvent返回false,那么它的父容器的onTouchEvent將會被調(diào)用,依次類推。如果所有的元素都不處理這個事件,那么這個事件將會最終傳遞給Activity處理, 即Activity的onTouchEvent方法會被調(diào)用。這個過程其實很好理解,我們可以換一種思路,假設(shè)點擊事件是一個難題,這個難題最終被上級領(lǐng)導(dǎo)分給了一個程序員去處理(這是事件分發(fā)過程),結(jié)果這個程序員搞不定(onTouchEvent返回了false),現(xiàn)在該怎么辦呢?難題必須要解決,那就只能交給水平更高的上級解決(上級的onTouchEvent被調(diào)用),如果上級再搞不定,那就只能交給上級的上級去解決,就這樣難題一層層地向上拋,這是公司內(nèi)部一種常見的處理問題的過程。

關(guān)于事件傳遞機制還需要注意以下:

  1. 同一見事件序列是從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結(jié)束,在這個過程中所產(chǎn)生的一系列事件,這個事件的序列以down開始,中間含有數(shù)量不定的move事件,最終以up事件結(jié)束。

  2. 正常情況下,一個事件序列只能被一個View攔截且消耗。這一條的原因可以參考(3),因為一旦一個元素攔截了某個事件,那么同一個事件序列的所有事件都會直接交給它處理,因此同一個事件序列中的事件不能分別由兩個View同時處理,但是通過特殊手段可以做到,比如
    一個View將本該自己處理的事件通過onTouchEvent強行傳遞給其他View處理。

  3. 某個View一旦決定攔截,那么這個事件序列都只能由它來處理(如果事件序列能夠傳遞給它的話),并且它的onInterceptTouchEvent不會被調(diào)用。這條也很好理解,就是說當(dāng)一個View決定攔截一個事件后,那么系統(tǒng)會把同一個事件序列內(nèi)的其他方法都直接交給它來處理,因此就不用再調(diào)用這個View的onInterceptTouchEvent去詢問它是否攔截了。

  4. 某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一件序列中的其他事件都不會再交給它處理,并且事件 將重新交由它的父元素去處理,即父元素的onTouchEvent會被調(diào)用。意思就是事件一旦交給一個View處理,那么它就必須消耗掉,否則同一事件序列中剩下的事件就不再交給它處理了,這就好比上級交給程序員一件事,如果這件事沒有處理好,短時間內(nèi)上級就不敢再把事件交給這個程序員做了,二者是類似的道理。

  5. 如果View不消耗ACTION_DOWN以外的事件,那么這個點擊事件會消失,此時父元素的onTouchEvent并不會調(diào)用,并且當(dāng)前View可以持續(xù)收到后續(xù)的事件,最終這些消失的點擊事件會傳遞給Activity處理。

  6. ViewGroup默認不攔截任何事件。Android源碼中ViewGroup的onInterceptTouchEvent方法默認返回false。

  7. View沒有onInterceptTouchEvent方法,一旦點擊事件傳遞給它,那么它的onTouchEvent方法就會被調(diào)用。

  8. View的onTouchEvent默認都會消耗事件(返回true),除非它是不可點擊的(clickable和longClickable同時為false)。View的longClickable屬性默認為false,clickable屬性要分情況,比如Button的clickable屬性默認為true,而TextView的clickable屬性默認為false。

  9. View的enable屬性不影響onTouchEvent的默認返回值。哪怕一個View是disable狀態(tài)的,只要它的clickable或者longClickable有一個為true,那么它的onTouchEvent就返回true。

  10. onClick會發(fā)生的前提是當(dāng)前View是可點擊的,并且它接收到了down和up事件。

  11. 事件傳遞過程是由外向內(nèi)的,即事件總是先傳遞給父元素,然后再由父元素分發(fā)給子View,通過requestDisallowInterTouchEvent方法可以在子元素中干預(yù)父元素的事件分發(fā)過程,但是ACTION_DOWN事件除外。

3. 事件分發(fā)的流程

Activity–>PhoneWindow–>DecorView–>ViewGroup–>…–>View(View樹最底部的View)


事件分發(fā)

十三 ListView

1. ListView是什么?

ListView就是能用一個數(shù)據(jù)集合以動態(tài)滾動的方式展示到用戶界面上的View。

2. ListView的適配器

  • ArrayAdapter:支持泛型操作,最簡單的一個Adapter,只能展現(xiàn)一行文字
  • SimpleAdapter:同樣具有良好擴展性的一個Adapter,可以自定義多種效果
  • BaseAdapter:抽象類,實際開發(fā)中我們會繼承這個類并且重寫相關(guān)方法,用得最多的一個Adapter

BaseAdapter是開發(fā)中最常用的適配器ArrayAdapter, SimpleAdapter 都繼承于BaseAdapter。BaseAdapter可以完成自己定義的Adapter,可以將任何復(fù)雜組合的數(shù)據(jù)和資源,以任何你想要的顯示效果展示給用戶。

繼承BaseAdapter之后,需要重寫以下四個方法:getCount,getItem,getItemId,getView。

系統(tǒng)在繪制ListView之前,將會先調(diào)用getCount方法來獲取Item的個數(shù)。每繪制一個Item就會調(diào)用一次getView方法,在getView中引用事先定義好的layout布局確定顯示的效果并返回一個View對象作為一個Item顯示出來。

這兩個方法是自定ListView顯示效果中最為重要的,同時只要重寫好了這兩個方法,ListView就能完全按開發(fā)者的要求顯示。而getItem和getItemId方法將會在調(diào)用ListView的響應(yīng)方法的時候被調(diào)用到。

3. ListView的recycleBin機制

在某一時刻,我們看到ListView中有許多View呈現(xiàn)在UI上,這些View對我們來說是可見的,這些可見的View可以稱作OnScreen的View,即在屏幕中能看到的View,也可以叫做ActiveView,因為它們是在UI上可操作的。

當(dāng)觸摸ListView并向上滑動時,ListView上部的一些OnScreen的View位置上移,并移除了ListView的屏幕范圍,此時這些OnScreen的View就變得不可見了,不可見的View叫做OffScreen的View,即這些View已經(jīng)不在屏幕可見范圍內(nèi)了,也可以叫做ScrapView,Scrap表示廢棄的意思,ScrapView的意思是這些OffScreen的View不再處于可以交互的Active狀態(tài)了。ListView會把那些ScrapView(即OffScreen的View)刪除,這樣就不用繪制這些本來就不可見的View了,同時,ListView會把這些刪除的ScrapView放入到RecycleBin中存起來,就像把暫時無用的資源放到回收站一樣。

當(dāng)ListView的底部需要顯示新的View的時候,會從RecycleBin中取出一個ScrapView,將其作為convertView參數(shù)傳遞給Adapter的getView方法,從而達到View復(fù)用的目的,這樣就不必在Adapter的getView方法中執(zhí)行LayoutInflater.inflate()方法了。

RecycleBin中有兩個重要的View數(shù)組,分別是mActiveViews和mScrapViews。這兩個數(shù)組中所存儲的View都是用來復(fù)用的,只不過mActiveViews中存儲的是OnScreen的View,這些View很有可能被直接復(fù)用;而mScrapViews中存儲的是OffScreen的View,這些View主要是用來間接復(fù)用的。

4. ListView優(yōu)化

  • convertView重用機制

  • ViewHolder機制

  • 三級緩沖/滑動監(jiān)聽事件

優(yōu)化一:在Adapter中的getView方法中使用ConvertView,即ConvertView的復(fù)用,不需要每次都inflate一個View出來,這樣既浪費時間,又浪費內(nèi)存。

優(yōu)化二:使用ViewHolder,不要在getView方法中寫findViewById方法,因為getView方法會執(zhí)行很多遍,這樣也可以節(jié)省時間,節(jié)約內(nèi)存。

優(yōu)化三:使用分頁加載,講真實際開發(fā)中,ListView的數(shù)據(jù)肯定不止幾百條,成千上萬條數(shù)據(jù)你不可能一次性加載出來,所以這里需要用到分頁加載,一次加載幾條或者十幾條,但是如果數(shù)據(jù)量很大的話,像qq,微信這種,如果順利加載到最后面的話,那么你的list中也會有幾萬甚至幾十萬的數(shù)據(jù),這樣可能也會導(dǎo)致OOM,所以你的數(shù)據(jù)集List中也不能有那么多數(shù)據(jù),所以每加載一頁的時候你可以覆蓋前一頁的數(shù)據(jù)。

優(yōu)化四:如果數(shù)據(jù)當(dāng)中有圖片的話,使用第三方庫來加載(也就是緩存),如果你的能力強大到能自己維護的話,那也不是不可以。

優(yōu)化五:當(dāng)你手指在滑動列表的時候,盡可能的不加載圖片,這樣的話滑動就會更加流暢。

十四 動畫

1.Android動畫的分類

1.1 補間動畫

a.漸變動畫支持四種類型:平移(Translate)、旋轉(zhuǎn)(Rotate)、縮放(Scale)、不透明度

b. 只是顯示的位置變動,View的實際位置未改變,表現(xiàn)為View移動到其他地方,點擊事件仍在原處才能響應(yīng)。

c. 組合使用步驟較復(fù)雜。

d. View Animation 也是指此動畫。

1.2 幀動畫

a. 用于生成連續(xù)的Gif效果圖。

b. DrawableAnimation也是指此動畫

1.3 屬性動畫

a.支持對所有View能更新的屬性的動畫(需要屬性的setXxx()和getXxx())。

b. 更改的是View實際的屬性,所以不會影響其在動畫執(zhí)行后所在位置的正常使用。

c. Android3.0(API11)及以后出現(xiàn)的功能,3.0之前的版本可使用github第三方開源庫nineoldandroids.jar進行支持。

2.補間動畫,幀動畫,屬性動畫優(yōu)缺點

2.1 補間動畫優(yōu)缺點

缺點:當(dāng)平移動畫執(zhí)行完停在最后的位置,結(jié)果焦點還在原來的位置(控件的屬性沒有真的被改變)
優(yōu)點:相對于逐幀動畫來說,補間動畫更為連貫自然

2.2 幀動畫優(yōu)缺點

缺點:效果單一,逐幀播放需要很多圖片,占用控件較大
優(yōu)點:制作簡單

2.3 屬性動畫優(yōu)缺點

缺點:(3.0+API出現(xiàn))向下兼容問題。
優(yōu)點:易定制,效果強。

十五 自定義View

1. 自定義View的幾種方式

  • 對原View進行擴展方式

  • 多個View的組合方式

  • 重寫View的方式

2.自定義View需要重寫的方法。

  • onMesure(測量)
  • onLayout(布局)
  • onDraw(繪制)

十六 Context

Context的中文翻譯為:語境; 上下文; 背景; 環(huán)境,在開發(fā)中我們經(jīng)常說稱之為“上下文”,那么這個“上下文”到底是指什么意思呢?在語文中,我們可以理解為語境,在程序中,我們可以理解為當(dāng)前對象在程序中所處的一個環(huán)境,一個與系統(tǒng)交互的過程。比如微信聊天,此時的“環(huán)境”是指聊天的界面以及相關(guān)的數(shù)據(jù)請求與傳輸,Context在加載資源、啟動Activity、獲取系統(tǒng)服務(wù)、創(chuàng)建View等操作都要參與。

Context提供了關(guān)于應(yīng)用環(huán)境全局信息的接口。它是一個抽象類,它的執(zhí)行被Android系統(tǒng)所提供。它允許獲取以應(yīng)用為特征的資源和類型,是一個統(tǒng)領(lǐng)一些資源(應(yīng)用程序環(huán)境變量等)的上下文。就是說,它描述一個應(yīng)用程序環(huán)境的信息(即上下文);是一個抽象類,Android提供了該抽象類的具體實現(xiàn)類;通過它我們可以獲取應(yīng)用程序的資源和類(包括應(yīng)用級別操作,如啟動Activity,發(fā)廣播,接受Intent等)。

Context.png

Context的作用域:


Context的作用域.png

十七 ContentProvider

1. 簡介

內(nèi)容提供者(Content Provider)主要用于在不同的應(yīng)用程序之間實現(xiàn)數(shù)據(jù)共享的功能,它提供了一套完整的機制,允許一個程序訪問另一個程序中的數(shù)據(jù),同時還能保證被訪數(shù)據(jù)的安全性。目前,使用內(nèi)容提供者是Android實現(xiàn)跨程序共享數(shù)據(jù)的標(biāo)準(zhǔn)方式。
不同于文件存儲和SharedPreferences存儲中的兩種全局可讀可寫操作模式,內(nèi)容提供者可以選擇只對哪一部分數(shù)據(jù)進行共享,從而保證我們程序中的隱私數(shù)據(jù)不會泄露的風(fēng)險。

2.通過內(nèi)容提供者訪問其他程序的數(shù)據(jù)

內(nèi)容提供者的用法一般有兩種,一種是使用現(xiàn)有的內(nèi)容提供器來讀取和操作相應(yīng)程序中的數(shù)據(jù),另一種是創(chuàng)建自己的內(nèi)容提供者來給我們的程序提供外部訪問接口。如果一個程序通過內(nèi)容提供者對其數(shù)據(jù)提供外部訪問接口,那么任何其他的應(yīng)用程序就都可以對這部分數(shù)據(jù)進行訪問。Android系統(tǒng)中自帶的電話簿,短信,媒體庫等程序都提供了類似的訪問接口,這就使得第三方應(yīng)用程序可以充分利用這部分數(shù)據(jù)來實現(xiàn)更好的功能。下面我們就來看看如何通過內(nèi)容提供者訪問其他程序的數(shù)據(jù):

2.1 ContentResolver的基本用法

想要訪問內(nèi)容提供者中共享的數(shù)據(jù),就一定要借助CotentResolver類,可以通過Context中的getContentResolver()方法獲取該類的實例。ContentResolver中提供了一系列的方法用于對數(shù)據(jù)進行CRUD(增刪改查)操作,其中insert()方法用于添加數(shù)據(jù),update()方法用于數(shù)據(jù)的更新,delete()方法用于數(shù)據(jù)的刪除,query()方法用于數(shù)據(jù)的查詢。這好像SQLite數(shù)據(jù)庫操作有木有?
??不同于SQLiteDatabase,ContentResolver中的增刪改查方法都是不接收表名參數(shù)的,而是使用一個Uri的參數(shù)代替,這個參數(shù)被稱作內(nèi)容URI。內(nèi)容URI給內(nèi)容提供者中的數(shù)據(jù)建立了唯一的標(biāo)識符,它主要由兩部分組成:authority和path。authority是用于對不同的應(yīng)用程序做區(qū)分的,一般為了避免沖突,都會采用程序包名的方式來進行命名。比如某個程序的包名為com.example.app,那么該程序?qū)?yīng)的authority就可以命名為com.example.app.provider。path則是用于對同一應(yīng)用程序中不同的表做區(qū)分的,通常都會添加到authority的后面。比如某個程序的數(shù)據(jù)庫里存在兩張表:table1和table2,這時就可以將path分別命名為/table1和/table2,然后把authority和path進行組合,內(nèi)容的URI就變成了com.example.app.provider/table1和com.example.app.provider/table2。不過目前還是很難辨認出這兩個字符串就是兩個內(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對象才可以作為參數(shù)傳入。解析的方法也相當(dāng)簡單,代碼如下所示:

Uri uri = new Uri.parse("content://com.example.app.provider/table1");

只需要調(diào)用Uri的靜態(tài)方法parse()就可以把內(nèi)容URI字符串解析成URI對象。
??現(xiàn)在,我們可以通過這個Uri對象來查詢table1表中的數(shù)據(jù)了。代碼如下所示:

Cursor cursor = getContentResolver()
                .query(
                       uri,projection,selection,selectionArgs,sortOrder
                 );

query()方法接收的參數(shù)跟SQLiteDatabase中的query()方法接收的參數(shù)很像,但總體來說這個稍微簡單一些,畢竟這是在訪問其他程序中的數(shù)據(jù),沒必要構(gòu)建復(fù)雜的查詢語句。下標(biāo)對內(nèi)容提供者中的query的接收的參數(shù)進行了詳細的解釋:

查詢完成仍然會返回一個Cursor對象,這時我們就可以將數(shù)據(jù)從Cursor對象中逐個讀取出來了。讀取的思路仍然是對這個Cursor對象進行遍歷,然后一條一條的取出數(shù)據(jù)即可,代碼如下:

if(cursor != null){//注意這里一定要進行一次判空,因為有可能你要查詢的表根本不存在
    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)提到過,如果要想實現(xiàn)跨程序共享數(shù)據(jù)的功能,官方推薦的方式就是使用內(nèi)容提供器,可以新建一個類去繼承ContentProvider類的方式來創(chuàng)建一個自己的內(nèi)容提供器。ContentProvider類有6個抽象方法,我們在使用子類繼承它的時候,需要將這6個方法全部重寫。新建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;
    }
}
  1. onCreate()方法:
    ??初始化內(nèi)容提供器的時候調(diào)用。通常會在這里完成對數(shù)據(jù)庫的創(chuàng)建和升級等操作。返回true表示內(nèi)容提供器初始化成功,返回false則表示失敗。注意,只有當(dāng)存在ContentResolver嘗試訪問我們的程序中的數(shù)據(jù)時,內(nèi)容提供器才會被初始化。

  2. query()方法:
    ??從內(nèi)容提供器中查詢數(shù)據(jù)。使用uri參數(shù)來確定查詢的哪張表,projection參數(shù)用于確定查詢的哪一列,selection和selectionArgs參數(shù)用于約束查詢哪些行,sortOrder參數(shù)用于對結(jié)果進行排序,查詢的結(jié)果存放在Cursor對象中返回。

  3. insert()方法:
    ??向內(nèi)容提供器中添加一條數(shù)據(jù)。使用uri參數(shù)來確定要添加的表,待添加的數(shù)據(jù)保存在values參數(shù)中。添加完成后,返回一個用于表示這條新紀(jì)錄的URI。

  4. update()方法:
    ??更新內(nèi)容提供器中已有的數(shù)據(jù)。使用uri參數(shù)來確定更新哪一張表中的數(shù)據(jù),新數(shù)據(jù)保存著values參數(shù)當(dāng)中,selection和selectionArgs參數(shù)用于約束更新哪些行,受影響的行數(shù)將作為返回值返回。

  5. delete()方法:
    ??從內(nèi)容提供器中刪除數(shù)據(jù)。使用uri參數(shù)來確定刪除哪一張表中的數(shù)據(jù),selection和selectionArgs參數(shù)用于約束刪除哪些行,被刪除的行數(shù)將作為返回值返回。

  6. getType()方法:
    ??根據(jù)傳入的內(nèi)容URI來返回相應(yīng)的MIME類型。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。