1. 程式人生 > >Android面試總結--Android篇

Android面試總結--Android篇

Activity生命週期

image

圖中需要注意一下幾點:

1.Activity例項是由系統自動建立,並在不同的狀態期間回撥相應的方法。一個最簡單的完整的Activity生命週期會按照如下順序回撥:onCreate -> onStart -> onResume -> onPause -> onStop -> onDestroy。稱之為entire lifetime。

2.當執行onStart回撥方法時,Activity開始被使用者所見(也就是說,onCreate時使用者是看不到此Activity的,那使用者看到的是哪個?當然是此Activity之前的那個Activity),一直到onStop之前,此階段Activity都是被使用者可見,稱之為visible lifetime。

3.當執行到onResume回撥方法時,Activity可以響應使用者互動,一直到onPause方法之前,此階段Activity稱之為foreground lifetime。

在實際應用場景中,假設A Activity位於棧頂,此時使用者操作,從A Activity跳轉到B Activity。那麼對AB來說,具體會回撥哪些生命週期中的方法呢?回撥方法的具體回撥順序又是怎麼樣的呢?

開始時,A被例項化,執行的回撥有A:onCreate -> A:onStart -> A:onResume

當用戶點選A中按鈕來到B時,假設B全部遮擋住了A,將依次執行A:onPause -> B:onCreate -> B:onStart -> B:onResume -> A:onStop

此時如果點選Back鍵,將依次執行B:onPause -> A:onRestart -> A:onStart -> A:onResume -> B:onStop -> B:onDestroy

橫豎屏切換時Activity的生命週期變化?

1、如果自己沒有配置android:ConfigChanges,這時預設讓系統處理,就會重建Activity,此時Activity的生命週期會走一遍,其中onSaveInstanceState() 與onRestoreIntanceState() 資源相關的系統配置發生改變或者資源不足:例如螢幕旋轉、切換系統語言,當前Activity會銷燬,並且在onStop之前回調onSaveInstanceState儲存資料,在重新建立Activity的時候在onStart之後回撥onRestoreInstanceState

。其中Bundle資料會傳到onCreate(不一定有資料)和onRestoreInstanceState(一定有資料)。 使用者或者程式設計師主動去銷燬一個Activity的時候不會回撥,其他情況都會呼叫,來儲存介面資訊。如程式碼中finish()或使用者按下back,不會回撥。

image

2、如果設定android:configChanges=“orientation|keyboardHidden|screenSize”>,此時Activity的生命週期不會重走一遍,Activity不會重建,只會回撥onConfigurationChanged方法。

Activity任務棧

任務棧是一種後進先出的結構。位於棧頂的Activity處於焦點狀態,當按下back按鈕的時候,棧內的Activity會一個一個的出棧,並且呼叫其onDestory()方法。如果棧內沒有Activity,那麼系統就會回收這個棧,每個APP預設只有一個棧,以APP的包名來命名.

棧與佇列的區別:

  1. 佇列先進先出,棧先進後出
  2. 對插入和刪除操作的"限定"。 棧是限定只能在表的一端進行插入和刪除操作的線性表。 佇列是限定只能在表的一端進行插入和在另一端進行刪除操作的線性表。
  3. 遍歷資料速度不同

Activity啟動模式

啟動模式可在AndroidManifest.xml中,通過標籤的android:launchMode屬性設定

  • standard模式

    • 特點:1.Activity的預設啟動模式 2.每啟動一個Activity就會在棧頂建立一個新的例項。例如:鬧鐘程式
    • 缺點:當Activity已經位於棧頂時,而再次啟動Activity時還需要在建立一個新的例項,不能直接複用。
  • singleTop模式

    • 特點:該模式會判斷要啟動的Activity例項是否位於棧頂,如果位於棧頂直接複用,否則建立新的例項。 例如:瀏覽器的書籤
    • 缺點:如果Activity並未處於棧頂位置,則可能還會建立多個例項。
  • singleTask模式

    • 特點:使Activity在整個應用程式中只有一個例項。每次啟動Activity時系統首先檢查棧中是否存在當前Activity例項,如果存在則直接複用,並把當前Activity之上所有例項全部出棧。例如:瀏覽器主介面
  • singleInstance模式

    • 特點:該模式的Activity會啟動一個新的任務棧來管理Activity例項,並且該例項在整個系統中只有一個。無論從那個任務棧中啟動該Activity,都會是該Activity所在的任務棧轉移到前臺,從而使Activity顯示。主要作用是為了在不同程式中共享一個Activity例項。

Intent Flags: Flags有很多,比如: Intent.FLAG_ACTIVITY_NEW_TASK 相當於singleTask Intent. FLAG_ACTIVITY_CLEAR_TOP 相當於singleTop

Activity啟動流程

  • app啟動的過程有兩種情況,第一種是從桌面launcher上點選相應的應用圖示,第二種是在activity中通過呼叫startActivity來啟動一個新的activity。
  • 我們建立一個新的專案,預設的根activity都是MainActivity,而所有的activity都是儲存在堆疊中的,我們啟動一個新的activity就會放在上一個activity上面,而我們從桌面點選應用圖示的時候,由於launcher本身也是一個應用,當我們點選圖示的時候,系統就會呼叫startActivitySately(),一般情況下,我們所啟動的activity的相關資訊都會儲存在intent中,比如action,category等等。
  • 我們在安裝這個應用的時候,系統也會啟動一個PackageManagerService的管理服務,這個管理服務會對AndroidManifest.xml檔案進行解析,從而得到應用程式中的相關資訊,比如service,activity,Broadcast等等,然後獲得相關元件的資訊。
  • 當我們點選應用圖示的時候,就會呼叫startActivitySately()方法,而這個方法內部則是呼叫startActivty(),而startActivity()方法最終還是會呼叫startActivityForResult()這個方法。而在startActivityForResult()這個方法。因為startActivityForResult()方法是有返回結果的,所以系統就直接給一個-1,就表示不需要結果返回了。
  • 而startActivityForResult()這個方法實際是通過Instrumentation類中的execStartActivity()方法來啟動activity,Instrumentation這個類主要作用就是監控程式和系統之間的互動。而在這個execStartActivity()方法中會獲取ActivityManagerService的代理物件,通過這個代理物件進行啟動activity。
  • 啟動就會呼叫一個checkStartActivityResult()方法,如果說沒有在配置清單中配置有這個元件,就會在這個方法中丟擲異常了
  • 當然最後呼叫的是Application.scheduleLaunchActivity()進行啟動activity,而這個方法中通過獲取得到一個ActivityClientRecord物件,而這個ActivityClientRecord通過handler來進行訊息的傳送,系統內部會將每一個activity元件使用ActivityClientRecord物件來進行描述,而ActivityClientRecord物件中儲存有一個LoaderApk物件,通過這個物件呼叫handleLaunchActivity來啟動activity元件,而頁面的生命週期方法也就是在這個方法中進行呼叫

Service生命週期

image

  • 被啟動的服務的生命週期:如果一個Service被某個Activity 呼叫 Context.startService 方法啟動,那麼不管是否有Activity使用bindService繫結或unbindService解除繫結到該Service,該Service都在後臺執行。如果一個Service被startService 方法多次啟動,那麼onCreate方法只會呼叫一次,onStart將會被呼叫多次(對應呼叫startService的次數),並且系統只會建立Service的一個例項(因此你應該知道只需要一次stopService呼叫)。該Service將會一直在後臺執行,而不管對應程式的Activity是否在執行,直到被呼叫stopService,或自身的stopSelf方法。當然如果系統資源不足,android系統也可能結束服務。

  • 被繫結的服務的生命週期:如果一個Service被某個Activity 呼叫 Context.bindService 方法繫結啟動,不管呼叫 bindService 呼叫幾次,onCreate方法都只會呼叫一次,同時onStart方法始終不會被呼叫。當連線建立之後,Service將會一直執行,除非呼叫Context.unbindService 斷開連線或者之前呼叫bindService 的 Context 不存在了(如Activity被finish的時候),系統將會自動停止Service,對應onDestroy將被呼叫。

  • 被啟動又被繫結的服務的生命週期:如果一個Service又被啟動又被繫結,則該Service將會一直在後臺執行。並且不管如何呼叫,onCreate始終只會呼叫一次,對應startService呼叫多少次,Service的onStart便會呼叫多少次。呼叫unbindService將不會停止Service,而必須呼叫 stopService 或 Service的 stopSelf 來停止服務。

  • 當服務被停止時清除服務:當一個Service被終止(1、呼叫stopService;2、呼叫stopSelf;3、不再有繫結的連線(沒有被啟動))時,onDestroy方法將會被呼叫,在這裡你應當做一些清除工作,如停止在Service中建立並執行的執行緒。

生命週期方法簡單介紹

  • startService() 作用:啟動Service服務 手動呼叫startService()後,自動呼叫內部方法:onCreate()、onStartCommand() 如果一個service被startService多次啟動,onCreate()只會呼叫一次 onStartCommand()呼叫次數=startService()次數

  • stopService() 作用:關閉Service服務 手動呼叫stopService()後,自動呼叫內部方法:onDestory() 如果一個service被啟動且被繫結,如果沒有在繫結的前提下stopService()是無法停止服務的。

  • bindService() 作用:繫結Service服務 手動呼叫bindService()後,自動呼叫內部方法:onCreate()、onBind()

  • unbindService() 作用:解綁Service服務 手動呼叫unbindService()後,自動呼叫內部方法:onCreate()、onBind()、onDestory()

注意: startService()和stopService()只能開啟和關閉Service,無法操作Service; bindService()和unbindService()可以操作Service startService開啟的Service,呼叫者退出後Service仍然存在; BindService開啟的Service,呼叫者退出後,Service隨著呼叫者銷燬。

介紹Service

Service一般分為兩種:

  • 本地服務, Local Service 用於應用程式內部。在Service可以呼叫Context.startService()啟動,呼叫Context.stopService()結束。 在內部可以呼叫Service.stopSelf() 或 Service.stopSelfResult()來自己停止。無論呼叫了多少次startService(),都只需呼叫一次 stopService()來停止。

  • 遠端服務, Remote Service 用於android系統內部的應用程式之間。可以定義介面並把介面暴露出來,以便其他應用進行操作。客戶端建立到服務物件的連線,並通過那個連線來呼叫服 務。呼叫Context.bindService()方法建立連線,並啟動,以呼叫 Context.unbindService()關閉連線。多個客戶端可以繫結至同一個服務。如果服務此時還沒有載入,bindService()會先加 載它。 提供給可被其他應用複用,比如定義一個天氣預報服務,提供與其他應用呼叫即可。

1、執行緒間通訊

我們知道執行緒是CPU排程的最小單位。在Android中主執行緒是不能夠做耗時操作的,子執行緒是不能夠更新UI的。而執行緒間通訊的方式有很多,比如廣播,Eventbus,介面回掉,在Android中主要是使用handler。handler通過呼叫sendmessage方法,將儲存訊息的Message傳送到Messagequeue中,而looper物件不斷的呼叫loop方法,從messageueue中取出message,交給handler處理,從而完成執行緒間通訊。

2、Android執行緒池

Android中常見的執行緒池有四種,FixedThreadPoolCachedThreadPoolScheduledThreadPoolSingleThreadExecutor

  • FixedThreadPool

執行緒池是通過Executors的new FixedThreadPool方法來建立。它的特點是該執行緒池中的執行緒數量是固定的。即使執行緒處於閒置的狀態,它們也不會被回收,除非執行緒池被關閉。當所有的執行緒都處於活躍狀態的時候,新任務就處於佇列中等待執行緒來處理。注意,FixedThreadPool只有核心執行緒,沒有非核心執行緒。

  • CachedThreadPool

執行緒池是通過Executors的new CachedThreadPool進行建立的。它是一種執行緒數目不固定的執行緒池,它沒有核心執行緒,只有非核心執行緒,當執行緒池中的執行緒都處於活躍狀態,就會建立新的執行緒來處理新的任務。否則就會利用閒置的執行緒來處理新的任務。執行緒池中的執行緒都有超時機制,這個超時機制時長是60s,超過這個時間,閒置的執行緒就會被回收。這種執行緒池適合處理大量並且耗時較少的任務。這裡得說一下,CachedThreadPool的任務佇列,基本都是空的。

  • ScheduledThreadPool

執行緒池是通過Executors的new ScheduledThreadPool進行建立的,它的核心執行緒是固定的,但是非核心執行緒數是不固定的,並且當非核心執行緒一處於空閒狀態,就立即被回收。這種執行緒適合執行定時任務和具有固定週期的重複任務。

  • SingleThreadExecutor

執行緒池是通過Executors的new SingleThreadExecutor方法來建立的,這類執行緒池中只有一個核心執行緒,也沒有非核心執行緒,這就確保了所有任務能夠在同一個執行緒並且按照順序來執行,這樣就不需要考慮執行緒同步的問題。

3、AsyncTask的工作原理

AsyncTask是Android本身提供的一種輕量級的非同步任務類。它可以線上程池中執行後臺任務,然後把執行的進度和最終的結果傳遞給主執行緒更新UI。實際上,AsyncTask內部是封裝了Thread和Handler。雖然AsyncTask很方便的執行後臺任務,以及在主執行緒上更新UI,但是,AsyncTask並不合適進行特別耗時的後臺操作,對於特別耗時的任務,個人還是建議使用執行緒池。

AsyncTask提供有4個核心方法

1、onPreExecute():該方法在主執行緒中執行,在執行非同步任務之前會被呼叫,一般用於一些準備工作。

2、doInBackground(String… params):這個方法是線上程池中執行,此方法用於執行非同步任務。在這個方法中可以通過publishProgress方法來更新任務的進度,publishProgress方法會呼叫onProgressUpdate方法,另外,任務的結果返回給onPostExecute方法。

3、onProgressUpdate(Object… values):該方法在主執行緒中執行,主要用於任務進度更新的時候,該方法會被呼叫。

4、onPostExecute(Long aLong):在主執行緒中執行,在非同步任務執行完畢之後,該方法會被呼叫,該方法的引數及為後臺的返回結果。

除了這幾個方法之外還有一些不太常用的方法,如onCancelled(),在非同步任務取消的情況下,該方法會被呼叫。

原始碼可以知道從上面的execute方法內部呼叫的是executeOnExecutor()方法,即executeOnExecutor(sDefaultExecutor, params);而sDefaultExecutor實際上是一個序列的執行緒池。而onPreExecute()方法在這裡就會被呼叫了。

接著看這個執行緒池。AsyncTask的執行是排隊執行的,因為有關鍵字synchronized,而AsyncTask的Params引數就封裝成為FutureTask類,FutureTask這個類是一個併發類,在這裡它充當了Runnable的作用。

接著FutureTask會交給SerialExecutor的execute方法去處理,而SerialExecutor的executor方法首先就會將FutureTask新增到mTasks佇列中,如果這個時候沒有任務,就會呼叫scheduleNext()方法,執行下一個任務。如果有任務的話,則執行完畢後最後在呼叫scheduleNext()執行下一個任務。直到所有任務被執行完畢。

而AsyncTask的構造方法中有一個call()方法,而這個方法由於會被FutureTask的run方法執行,所以最終這個call方法會線上程池中執行。

而doInBackground這個方法就是在這裡被呼叫的。我們好好研究一下這個call()方法。mTaskInvoked.set(true)表示當前任務已經執行過了。接著執行doInBackground方法,最後將結果通過postResult(result)方法進行傳遞。

postResult()方法中通過sHandler來發送訊息,sHandler的中通過訊息的型別來判斷一個MESSAGE_POST_RESULT,這種情況就是呼叫onPostExecute(result)方法或者是onCancelled(result)。另一種訊息型別是MESSAGE_POST_PROGRESS則呼叫更新進度onProgressUpdate。

4、Binder的工作機制

  • 直觀來說,Binder是Android中的一個類,它實現了IBinder介面,
  • 從IPC的角度來說,Binder是Android中的一種跨程序通訊的一種方式,同時還可以理解為是一種虛擬的物理裝置,它的裝置驅動是dev/binder/。
  • 從Framework角度來說,Binder是ServiceManager連線各種Manager和相應ManagerService的橋樑。
  • 從應用層來說,Binder是客戶端和服務端進行通訊的媒介,當bindService的時候,服務端會返回一個包含服務端業務呼叫的Binder物件,通過這個Binder物件,客戶端就可以獲取服務端提供的服務或者資料;

我們先來了解一下這個類中每個方法的含義:

  • DESCRIPTOR:Binder的唯一標識,一般用於當前Binder的類名錶示。

  • asInterface(android.os.IBinder obj):用於將服務端的Binder物件轉換成客戶端所需的AIDL介面型別的物件,這種轉化過程是區分程序的,如果客戶端和服務端位於同一個程序,那麼這個方法返回的是服務端的stub物件本身,否則返回的是系統封裝後的Stub.proxy物件。

  • asBinder():用於返回當前Binder物件。

  • onTransact:該方法執行在服務端的Binder執行緒池中,當客戶端發起跨程序通訊請求的時候,遠端請求通過系統底層封裝後交給該方法處理。注意這個方法public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服務端通過code可以確定客戶端所請求的目標方法是什麼,接著從data中取出目標方法所需的引數,然後執行目標方法。當目標方法執行完畢後,就像reply中寫入返回值。這個方法的執行過程就是這樣的。如果這個方法返回false,客戶端是會請求失敗的,所以我們可以在這個方法中做一些安全驗證。

image

Binder的工作機制但是要注意一些問題

  • 1、當客戶端發起請求時,由於當前執行緒會被掛起,直到服務端返回資料,如果這個遠端方法很耗時的話,那麼是不能夠在UI執行緒,也就是主執行緒中發起這個遠端請求的。

  • 2、由於Service的Binder方法執行線上程池中,所以Binder方法不管是耗時還是不耗時都應該採用同步的方式,因為它已經執行在一個執行緒中了。

Binder機制具體有兩層含義:

  • Binder是一種跨程序通訊(IPC,Inter-Process Communication)的手段;
  • Binder是一種遠端過程呼叫(RPC,Remote Procedure Call)的手段。

另一種理解: Binde機制簡單理解: 在Android系統的Binder機制中,是有Client,Service,ServiceManager,Binder驅動程式組成的,其中Client,service,Service Manager執行在使用者空間,Binder驅動程式是執行在核心空間的。而Binder就是把這4種元件粘合在一塊的粘合劑,其中核心的元件就是Binder驅動程式,Service Manager提供輔助管理的功能,而Client和Service正是在Binder驅動程式和Service Manager提供的基礎設施上實現C/S 之間的通訊。其中Binder驅動程式提供裝置檔案/dev/binder與使用者控制元件進行互動, Client、Service,Service Manager通過open和ioctl檔案操作相應的方法與Binder驅動程式進行通訊。而Client和Service之間的程序間通訊是通過Binder驅動程式間接實現的。而Binder Manager是一個守護程序,用來管理Service,並向Client提供查詢Service介面的能力。

5、View的繪製流程

Android自定義view,我們都知道實現有三部曲:onMeasure()onLayout()onDraw()

View的繪製流程是從viewRoot的perfromTraversal方法開始的。它經過measure,layout,draw方法才能夠將view繪製出來。其中 measure是測量寬高的,layout是確定view在父容器上的擺佈位置的,draw是將view繪製到螢幕上的。

Measure:

view的測量是需要MeasureSpc(測量規格),它代表一個32位int值,高2位代表SpecMode(測量模式),低(30)位的代表SpecSize(某種測量模式下的規格大小)。而一組SpecMode和SpeSize可以打包為一個MeasureSpec,反之,MeasureSpec可以解包得到SpecMode和SpeSize的值。SpecMode有三類:

  • unSpecified:父容器不對view有任何限制,要多大有多大。一般系統用這個多。

  • Exactly:父容器已經檢測出view所需要的精確大小,這個時候,view的大小就是SpecSize所指定的值,它對應著layout佈局中的math_parent或者是具體的數值

  • At_most:父容器指定了一個可變大小的SpecSize,view的大小不能夠大於這個值,它對應這佈局中的wrap_content. 對於普通的view,它的MeasureSpec是由父容器的MeasureSpec和自身的layoutParam共同決定的,一旦MeasureSpec確定後,onMeasure就可以確定view的寬高了。

View的measure過程:

onMeasure方法中有個setMeasureDimenSion方法來設定view的寬高測量值,而setMeasureDimenSion有個getDefaultSize()方法作為引數。一般情況下,我們只需要關注at_most和exactly兩種情況,getDefaultSize的返回值就是measureSpec中的SpecSize,而這個值基本就是view測量後的大小。而UnSpecified這種情況,一般是系統內部的測量過程,它是需要考慮view的背景這些因素的。

前面說的是view的測量過程,而viewGroup的measure過程:

對於viewGroup來說,除了完成自己的measure過程以外,還要遍歷去呼叫子類的measure方法,各個子元素在遞迴執行這個過程,viewGroup是一個抽象的類,沒有提供有onMeasure方法,但是提供了一個measureChildren的方法。measureChild方法的思想就是取出子元素的layoutParams,然後通過getChildMeasureSpec來常見子元素的MeasureSpec,然後子元素在measure方法進行測量。由於viewGroup子類有不同的佈局方式,導致他們的測量細節不一樣,所以viewGroup不能象view一樣呼叫onMeasure方法進行測量。

注意:在activity的生命週期中是沒有辦法正確的獲取view的寬高的,原因就是view沒有測量完。 1、在onWindowFocuschanged方法中獲取 ----該方法含義是view已經初始化完畢。 2、View.post()方法,將訊息佇列的尾部。 3、使用viewTreeObserver的回撥來完成。 4、通過view.measure方式手動測量。

View的Measure流程圖: image

onLayout

普通的view的話,可以通過setFrame方法來的到view四個頂點的位置,也就確定了view在父容器的位置,接著就呼叫onLayout方法,該方法是父容器確定子元素的位置。

onDraw

該方法就是將view繪製到螢幕上。分以下幾步

  • 繪製背景;
  • 繪製自己;
  • 繪製child;
  • 繪製裝飾;

總結一下View的繪製流程

  • 第一步:OnMeasure():測量檢視大小。從頂層父View到子View遞迴呼叫measure方法,measure方法又回撥OnMeasure。
  • 第二步:OnLayout():確定View位置,進行頁面佈局。從頂層父View向子View的遞迴呼叫view.layout方法的過程,即父View根據上一步measure子View所得到的佈局大小和佈局引數,將子View放在合適的位置上。
  • 第三步:OnDraw():繪製檢視。ViewRoot建立一個Canvas物件,然後呼叫OnDraw()。六個步驟:
    • ①、繪製檢視的背景;
    • ②、儲存畫布的圖層(Layer);
    • ③、繪製View的內容;
    • ④、繪製View子檢視,如果沒有就不用;
    • ⑤、還原圖層(Layer);
    • ⑥、繪製滾動條。

自定義控制元件:

  • 1、組合控制元件。這種自定義控制元件不需要我們自己繪製,而是使用原生控制元件組合成的新控制元件。如標題欄。
  • 2、繼承原有的控制元件。這種自定義控制元件在原生控制元件提供的方法外,可以自己新增一些方法。如製作圓角,圓形圖片。
  • 3、完全自定義控制元件:這個View上所展現的內容全部都是我們自己繪製出來的。比如說製作水波紋進度條。

Android中效能優化

由於手機硬體的限制,記憶體和CPU都無法像pc一樣具有超大的記憶體,Android手機上,過多的使用記憶體,會容易導致oom,過多的使用CPU資源,會導致手機卡頓,甚至導致anr。我主要是從一下幾部分進行優化:

佈局優化繪製優化記憶體洩漏優化響應速度優化listview優化bitmap優化執行緒優化

  • 佈局優化:工具 hierarchyviewer,解決方式

    • 1、刪除無用的空間和層級。

    • 2、選擇效能較低的viewgroup,如Relativelayout,如果可以選擇Relativelayout也可以使用LinearLayout,就優先使用LinearLayout,因為相對來說Relativelayout功能較為複雜,會佔用更多的CPU資源。

    • 3、使用標籤重用佈局,減少層級,進行預載入,使用的時候才載入。

  • 繪製優化

    • 繪製優化指view在ondraw方法中避免大量的耗時操作,由於ondraw方法可能會被頻繁的呼叫。

    • 1、ondraw方法中不要建立新的區域性變數,ondraw方法被頻繁的呼叫,很容易引起GC。

    • 2、ondraw方法不要做耗時操作。

  • 記憶體優化:參考記憶體洩漏

    • 單例模式

      context的使用,單列中傳入的是activity的context,在關閉activity時,activity的記憶體無法被回收,因為單列持有activity的引用 在context的使用上,應該傳入application的context到單列模式中,這樣就保證了單列的生命週期跟application的生命週期一樣

    • 非靜態內部類或者匿名內部類都有隱式持有外部類(通常是Activity)引起的記憶體洩漏;

      通常Handler、AnycTask、Thread等記憶體洩漏都是由於建立了非靜態內部類(或匿名內部類)並由子執行緒持有並執行耗時操作,導致Handler的生命週期比外部類的生命週期長;

    • BraodcastReceiver、File、Cursor等資源的使用未及時關閉

      應該在使用完成時主動銷燬,或者在Activity銷燬時登出BraodcastReceiver

    • MVP潛在的記憶體洩漏

      通常Presenter要同時持有View和Model的引用,如果Activity退出的時候,Presenter正在處理一些耗時操作,那麼Presenter的生命週期會比Activity長,導致Activity無法回收。因此,我們在使用MVP架構的時候要做好生命週期的維護,在View被銷燬的時候要通知Presenter釋放其持有

  • 響應優化

主執行緒不能做耗時操作,觸控事件5s,廣播10s,service20s。

  • listview優化

    • 1、getview方法中避免耗時操作。

    • 2、view的複用和viewholder的使用。

    • 3、滑動不適合開啟非同步載入。

    • 4、分頁處理資料。

    • 5、圖片使用三級快取。

  • Bitmap優化

    • 1、等比例壓縮圖片。

    • 2、不用的圖片,及時recycler掉

  • 執行緒優化

執行緒優化的思想是使用執行緒池來管理和複用執行緒,避免程式中有大量的Thread,同時可以控制執行緒的併發數,避免相互搶佔資源而導致執行緒阻塞。

  • 其他優化

    • 1、少用列舉,列舉佔用空間大。

    • 2、使用Android特有的資料結構,如SparseArray來代替hashMap。

    • 3、適當的使用軟引用和弱引用。

ContentProvider

概念:

  • ContentProvider是android四大元件之一,需要在Mainfest中註冊

  • ContentProvider為跨程序訪問資料提供介面(通訊錄、鬧鐘、相簿、視訊、聯絡人等等)

  • ContentProvider提供資料儲存的統一的介面(並不能實際儲存資料)

  • 資料的更新通知:使用ContentResolver

image

ContentProvider優缺點: 優點:

  • 為資料訪問提供統一的介面
  • 跨程序訪問

缺點:

  • 無法單獨使用,必須與其它的儲存方式結合使用(因為它不能儲存資料,而只是一個介面)

原理:

ContentProvider的底層原理 = Android中的Binder機制

URI定義

URI = content://com.ailian.provider/User/1

  • 主題名Schema:URI格式的字首(固定);
  • 授權資訊(Authority):唯一識別符號
  • 表名(Path):資料庫的表名
  • 記錄(ID):表中指定的某條記錄(不指定則返回全部)

BroadcastReceiver

  • 四大元件之一,(唯一一個可以不用再Mainfest註冊的,因為它支援動態註冊)

  • 動態註冊和靜態註冊

    image

靜態註冊在App首次啟動時,系統會自動例項化mBroadcastReceiver類,並註冊到系統中

原理:

Android中的廣播使用了設計模式中的觀察者模式

BroadcastReceiver模型中有3個角色:

  • 訊息訂閱者(廣播接收者)
  • 訊息釋出者(廣播發布者)
  • 訊息中心(AMS,即Activity Manager Service)

image

生命週期

BroadcastReceiver的生命週期與onReceive方法一致

呼叫流程

1、廣播接收器接收到相應廣播後,會自動回撥 onReceive() 方法 2、一般情況下,onReceive方法會涉及 與 其他元件之間的互動,如傳送Notification、啟動Service等 3、預設情況下,廣播接收器執行在 UI 執行緒,因此,onReceive()方法不能執行耗時操作,否則將導致ANR

廣播的型別

  • 普通廣播(Normal Broadcast) 如:自定義廣播等
  • 系統廣播(System Broadcast) 如:開機、網路變化、藍芽狀態變化、電量變化
  • 有序廣播(Ordered Broadcast)

使用方式:

sendOrderedBroadcast(intent);

注意點: 1、有序是針對廣播接收者而言的; 2、廣播接受者接收廣播的順序規則(同時面向靜態和動態註冊的廣播接受者) (1)、按照Priority屬性值從大到下排序;(2)、Priority屬性相同者,動態註冊的廣播優先;

特點:1、接收廣播按順序接收 先接收的廣播接收者可以對廣播進行截斷(可以使用abortBroadCast來攔截),即後接收的廣播接收者不再接收到此廣播; 先接收的廣播接收者可以對廣播進行修改(setResultExtras()),那麼後接收的廣播接收者將接收到被修改後的廣播(getResultExtras(true)))

  • 粘性廣播(Sticky Broadcast)

由於在Android5.0 & API 21中已經失效,所以不建議使用,在這裡也不作過多的總結。

  • App應用內廣播(Local Broadcast)

應用內廣播是指廣播的傳送者和接收者都同屬於一個App,使用方式:

1、註冊廣播時將exported屬性設定為false(exported對於有intent-filter情況下預設值為true),使得非本App內部發出的此廣播不被接收; 2、在廣播發送和接收時,增設相應許可權permission,用於許可權驗證; 3、傳送廣播時指定該廣播接收器所在的包名,此廣播將只會傳送到此包中的App內與之相匹配的有效廣播接收器中

注意

對於不同註冊方式的廣播接收器回撥OnReceive(Context context,Intent intent)中的context返回值是不一樣的:

  • 對於靜態註冊(全域性+應用內廣播),回撥onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
  • 對於全域性廣播的動態註冊,回撥onReceive(context, intent)中的context返回值是:Activity Context;
  • 對於應用內廣播的動態註冊(LocalBroadcastManager方式),回撥onReceive(context, intent)中的context返回值是:Application Context。
  • 對於應用內廣播的動態註冊(非LocalBroadcastManager方式),回撥onReceive(context, intent)中的context返回值是:Activity Context;

HttpClient與HttpUrlConnection的區別

此處延伸:Volley裡用的哪種請求方式(Volley2.3前HttpClient,2.3後HttpUrlConnection)

  • 首先HttpClient和HttpUrlConnection 這兩種方式都支援Https協議,都是以流的形式進行上傳或者下載資料,也可以說是以流的形式進行資料的傳輸,還有ipv6,以及連線池等功能。
  • HttpClient這個擁有非常多的API,所以如果想要進行擴充套件的話,並且不破壞它的相容性的話,很難進行擴充套件,也就是這個原因,Google在Android6.0的時候,直接就棄用了這個HttpClient. 而HttpUrlConnection相對來說就是比較輕量級了,API比較少,容易擴充套件,並且能夠滿足Android大部分的資料傳輸。
  • 比較經典的一個框架volley,在2.3版本以前都是使用HttpClient,在2.3以後就使用了HttpUrlConnection。

java虛擬機器和Dalvik虛擬機器的區別

  • Java虛擬機器: 1、java虛擬機器基於棧。 基於棧的機器必須使用指令來載入和操作棧上資料,所需指令更多更多。 2、java虛擬機器執行的是java位元組碼。(java類會被編譯成一個或多個位元組碼.class檔案)
  • Dalvik虛擬機器: 1、dalvik虛擬機器是基於暫存器的 2、Dalvik執行的是自定義的.dex位元組碼格式。(java類被編譯成.class檔案後,會通過一個dx工具將所有的.class檔案轉換成一個.dex檔案,然後dalvik虛擬機器會從其中讀取指令和資料 3、常量池已被修改為只使用32位的索引,以 簡化直譯器。 4、一個應用,一個虛擬機器例項,一個程序(所有android應用的執行緒都是對應一個linux執行緒,都執行在自己的沙盒中,不同的應用在不同的程序中執行。每個android dalvik應用程式都被賦予了一個獨立的linux PID(app_*))

程序保活

當前業界的Android程序保活手段主要分為黑、白、灰 三種,其大致的實現思路如下:

  • 黑色保活 所謂黑色保活,就是利用不同的app程序使用廣播來進行相互喚醒。舉個3個比較常見的場景:
    • 場景1:開機,網路切換、拍照、拍視訊時候,利用系統產生的廣播喚醒app
    • 場景2:接入第三方SDK也會喚醒相應的app程序,如微信sdk會喚醒微信,支付寶sdk會喚醒支付寶。由此發散開去,就會直接觸發了下面的場景3
    • 場景3:假如你手機裡裝了支付寶、淘寶、天貓、UC等阿里系的app,那麼你開啟任意一個阿里系的app後,有可能就順便把其他阿里系的app給喚醒了。(只是拿阿里打個比方,其實BAT系都差不多)
  • 白色保活 白色保活手段非常簡單,就是呼叫系統api啟動一個前臺的Service程序,這樣會在系統的通知欄生成一個Notification,用來讓使用者知道有這樣一個app在執行著,哪怕當前的app退到了後臺。如下方的LBE和QQ音樂這樣:
  • 灰色保活 灰色保活,這種保活手段是應用範圍最廣泛。它是利用系統的漏洞來啟動一個前臺的Service程序,與普通的啟動方式區別在於,它不會在系統通知欄處出現一個Notification,看起來就如同執行著一個後臺Service程序一樣。這樣做帶來的好處就是,使用者無法察覺到你執行著一個前臺程序(因為看不到Notification),但你的程序優先順序又是高於普通後臺程序的。那麼如何利用系統的漏洞呢,大致的實現思路和程式碼如下:
    • 思路一:API < 18,啟動前臺Service時直接傳入new Notification();
    • 思路二:API >= 18,同時啟動兩個id相同的前臺Service,然後再將後啟動的Service做stop處理

熟悉Android系統的童鞋都知道,系統出於體驗和效能上的考慮,app在退到後臺時系統並不會真正的kill掉這個程序,而是將其快取起來。開啟的應用越多,後臺快取的程序也越多。在系統記憶體不足的情況下,系統開始依據自身的一套程序回收機制來判斷要kill掉哪些程序,以騰出記憶體來供給需要的app。這套殺程序回收記憶體的機制就叫 Low Memory Killer ,它是基於Linux核心的 OOM Killer(Out-Of-Memory killer)機制誕生。 程序的重要性,劃分5級: 前臺程序 (Foreground process) 可見程序 (Visible process) 服務程序 (Service process) 後臺程序 (Background process) 空程序 (Empty process)

瞭解完 Low Memory Killer,再科普一下oom_adj。什麼是oom_adj?它是linux核心分配給每個系統程序的一個值,代表程序的優先順序,程序回收機制就是根據這個優先順序來決定是否進行回收。對於oom_adj的作用,你只需要記住以下幾點即可: 程序的oom_adj越大,表示此程序優先順序越低,越容易被殺回收;越小,表示程序優先順序越高,越不容易被殺回收 普通app程序的oom_adj>=0,系統程序的oom_adj才可能<0 有些手機廠商把這些知名的app放入了自己的白名單中,保證了程序不死來提高使用者體驗(如微信、QQ、陌陌都在小米的白名單中)。如果從白名單中移除,他們終究還是和普通app一樣躲避不了被殺的命運,為了儘量避免被殺,還是老老實實去做好優化工作吧。 所以,程序保活的根本方案終究還是回到了效能優化上,程序永生不死終究是個徹頭徹尾的偽命題!

Context的理解

  • Context是一個抽象基類。在翻譯為上下文,也可以理解為環境,是提供一些程式的執行環境基礎資訊。
  • Context下有兩個子類:ContextWrapper是上下文功能的封裝類,而ContextImpl則是上下文功能的實現類
    • ContextWrapper又有三個直接的子類, ContextThemeWrapper、Service和Application
      • ContextThemeWrapper是一個帶主題的封裝類,而它有一個直接子類就是Activity,所以Activity和Service以及Application的Context是不一樣的,只有Activity需要主題,Service不需要主題。
    • Context一共有三種類型,分別是Application、Activity和Service。這三個類雖然分別各種承擔著不同的作用,但它們都屬於Context的一種,而它們具體Context的功能則是由ContextImpl類去實現的,因此在絕大多數場景下,Activity、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊,比如啟動Activity,還有彈出Dialog。出於安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啟動必須要建立在另一個Activity的基礎之上,也就是以此形成的返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert型別的Dialog),因此在這種場景下,我們只能使用Activity型別的Context,否則將會出錯。
    • 每一個Activity和Service以及Application的Context都是一個新的ContextImpl物件 getApplication()用來獲取Application例項的,但是這個方法只有在Activity和Service中才能呼叫的到。那麼也許在絕大多數情況下我們都是在Activity或者Service中使用Application的,但是如果在一些其它的場景,比如BroadcastReceiver中也想獲得Application的例項,這時就可以藉助getApplicationContext()方法. getApplicationContext()比getApplication()方法的作用域會更廣一些,任何一個Context的例項,只要呼叫getApplicationContext()方法都可以拿到我們的Application物件。

getApplicationContext()和getApplication()方法得到的物件都是同一個application物件,只是物件的型別不一樣。 Context數量 = Activity數量 + Service數量 + 1 (1為Application)

怎麼在Service中建立Dialog對話方塊

1.在我們取得Dialog物件後,需給它設定型別,即:

dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)

2.在Manifest中加上許可權:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

理解Activity,View,Window三者關係

這裡先來個算是比較恰當的比喻來形容下它們的關係吧。Activity像一個工匠(控制單元),Window像窗戶(承載模型),View像窗花(顯示檢視)LayoutInflater像剪刀,Xml配置像窗花圖紙。

  • 1:Activity構造的時候會初始化一個Window,準確的說是PhoneWindow。
  • 2:這個PhoneWindow有一個“ViewRoot”,這個“ViewRoot”是一個View或者說ViewGroup,是最初始的根檢視。
  • 3:“ViewRoot”通過addView方法來一個個的新增View。比如TextView,Button等
  • 4:這些View的事件監聽,是由WindowManagerService來接受訊息,並且回撥Activity函式。比如onClickListener,onKeyDown等。

View、ViewGroup事件分發機制

  • 1.Touch事件分發中只有兩個主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三個相關事件。View包含dispatchTouchEvent、onTouchEvent兩個相關事件。其中ViewGroup又繼承於View。
  • 2.ViewGroup和View組成了一個樹狀結構,根節點為Activity內部包含的一個ViwGroup。
  • 3.觸控事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸控事件中,Down和Up都只有一個,Move有若干個,可以為0個。
  • 4.當Acitivty接收到Touch事件時,將遍歷子View進行Down事件的分發。ViewGroup的遍歷可以看成是遞迴的。分發的目的是為了找到真正要處理本次完整觸控事件的View,這個View會在onTouchuEvent結果返回true。
  • 5.當某個子View返回true時,會中止Down事件的分發,同時在ViewGroup中記錄該子View。接下去的Move和Up事件將由該子View直接進行處理。由於子View是儲存在ViewGroup中的,多層ViewGroup的節點結構時,上級ViewGroup儲存的會是真實處理事件的View所在的ViewGroup物件:如ViewGroup0-ViewGroup1-TextView的結構中,TextView返回了true,它將被儲存在ViewGroup1中,而ViewGroup1也會返回true,被儲存在ViewGroup0中。當Move和UP事件來時,會先從ViewGroup0傳遞至ViewGroup1,再由ViewGroup1傳遞至TextView。
  • 6.當ViewGroup中所有子View都不捕獲Down事件時,將觸發ViewGroup自身的onTouch事件。觸發的方式是呼叫super.dispatchTouchEvent函式,即父類View的dispatchTouchEvent方法。在所有子View都不處理的情況下,觸發Acitivity的onTouchEvent方法。
  • 7.onInterceptTouchEvent有兩個作用:1.攔截Down事件的分發。2.中止Up和Move事件向目標View傳遞,使得目標View所在的ViewGroup捕獲Up和Move事件。

Android中跨程序通訊的幾種方式

Android 跨程序通訊,像intent,contentProvider,廣播,service都可以跨程序通訊。

  • intent:這種跨程序方式並不是訪問記憶體的形式,它需要傳遞一個uri,比如說打電話。
  • contentProvider:這種形式,是使用資料共享的形式進行資料共享。
  • service:遠端服務,aidl
  • 廣播

Android中的幾種動畫

總的來說,Android動畫可以分為兩類,最初的傳統動畫和Android3.0 之後出現的屬性動畫; 傳統動畫又包括 幀動畫(Frame Animation)和補間動畫(Tweened Animation)

1、Tween Animation:

  • 補間動畫(檢視動畫)特性
    • 漸變動畫支援4種類型:平移(Translate)、旋轉(Rotate)、縮放(Scale)、不透明(Alpha)
    • 只是顯示的位置變動,View的實際位置未改變,表現為View移動到其他地方,點選事件仍在原處才可響應。
    • 組合使用步驟較複雜。
    • View Animation也是指此動畫。
    • 可以結合插值器(Interpolator)完整複雜動畫,Interpolator 主要作用是可以控制動畫的變化速率 ,就是動畫進行的快慢節奏。

通過Animation類和AnimationUtils配合實現的。動畫效果可以預先配置在res/anim目錄下的xml檔案中。

  • 補間動畫優缺點
    • 缺點:當平移動畫執行完停止最後的位置,結果焦點還在原來的位置(控制元件屬性未改變)。
    • 優點:相較於幀動畫,補間動畫更為連貫自然。

2、Frame Animation:

這種動畫(也叫Frame動畫、幀動畫)其實可以劃分到檢視動畫的類別,專門用來一個一個的顯示Drawable的resources,就像放幻燈片一樣,

  • 幀動畫特性
    • 用於生成連續的GIF動畫。
    • Drawable Animation也是指此動畫。
  • 幀動畫優缺點
    • 缺點:效果單一,逐幀播放需要很多圖片,佔用空間較大
    • 優點:製作簡單。

3、Property Animation:

  • 屬性動畫特性
    • 支援對所有View能更新的屬性的動畫(需要屬性的set/get方法)。
    • 更改的是View的實際屬性,不影響其動畫執行後所在位置的正常使用。
    • Android 3.0(API 11)及以後的功能。
  • 屬性動畫的優缺點
    • 缺點:向下相容的問題。
    • 優點:易定製,效果強。

4、視訊動畫

由UI設計師提供MP4視訊,通過Android的VideoView播放完成的動畫效果;

5、Lottie動畫

由UI設計師通過AE的Lottie外掛生成json資料並提供給Android或IOS開發者,由開發者整合完成的動畫;

Handler的原理

Android中主執行緒是不能進行耗時操作的,子執行緒是不能進行更新UI的。所以就有了handler,它的作用就是實現執行緒之間的通訊。 handler整個流程中,主要有四個物件,handler,Message,MessageQueue,Looper。當應用建立的時候,就會在主執行緒中建立handler物件, 我們通過要傳送的訊息儲存到Message中,handler通過呼叫sendMessage方法將Message傳送到MessageQueue中,Looper物件就會不斷的呼叫loop()方法 不斷的從MessageQueue中取出Message交給handler進行處理。從而實現執行緒之間的通訊。

Android熱修復原理

Android外掛化原理

Handler造成記憶體洩漏的原因和解決方案(AnycTask、Thread、Runable同理)

Handler造成記憶體洩漏的原因:

  • 當使用非靜態內部類(包括匿名類)來建立Handler的時候,Handler物件會隱式地持有一個外部類物件(通常是一個Activity)的引用(不然你怎麼可能通過Handler來操作Activity中的View?)。而Handler通常會伴隨著一個耗時的後臺執行緒(例如從網路拉取圖片)一起出現,這個後臺執行緒在任務執行完畢(例如圖片下載完畢)之後,通過訊息機制通知Handler,然後Handler把圖片更新到介面。然而,如果使用者在網路請求過程中關閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時被回收掉,但由於這時執行緒尚未執行完,而該執行緒持有Handler的引用(不然它怎麼發訊息給Handler?),這個Handler又持有Activity的引用,就導致該Activity無法被回收(即記憶體洩露),直到網路請求結束(例如圖片下載完畢)。
  • 如果你執行了Handler的postDelayed()方法,該方法會將你的Handler裝入一個Message,並把這條Message推到MessageQueue中,那麼在你設定的delay到達之前,會有一條MessageQueue -> Message -> Handler -> Activity的鏈,導致你的Activity被持有引用而無法被回收

對於Android應用來說,就是你的使用者開啟一個Activity,使用完之後關閉它,記憶體洩露;又開啟,又關閉,又洩露;幾次之後,程式佔用記憶體超過系統限制,FC。

另外,如果一組物件中只包含互相的引用,而沒有來自它們外部的引用(例如有兩個物件A和B互相持有引用,但沒有任何外部物件持有指向A或B的引用),這仍然屬於不可到達,同樣會被GC回收。 解決方案:

  • 方法一:通過程式邏輯來進行保護。

    • 在關閉Activity的時候停掉你的後臺執行緒。執行緒停掉了,就相當於切斷了Handler和外部連線的線,Activity自然會在合適的時候被回收。
    • 如果你的Handler是被delay的Message持有了引用,那麼使用相應的Handler的removeCallbacks()方法,把訊息物件從訊息佇列移除就行了。
  • 方法二:將Handler宣告為靜態類。 PS:在Java 中,非靜態的內部類和匿名內部類都會隱式地持有其外部類的引用,靜態的內部類不會持有外部類的引用。 靜態類不持有外部類的物件,所以你的Activity可以隨意被回收。由於Handler不再持有外部類物件的引用,導致程式不允許你在Handler中操作Activity中的物件了。所以你需要在Handler中增加一個對Activity的弱引用(WeakReference)。

static class MyHandler extends Handler
    {
        WeakReference<Activity> mWeakReference;
        public MyHandler(Activity activity) 
        {
            mWeakReference=new WeakReference<Activity>(activity);
        }
        @Override
        public void handleMessage(Message msg)
        {
            final Activity activity=mWeakReference.get();
            if(activity!=null)
            {
                if (msg.what == 1)
                {
                    noteBookAdapter.notifyDataSetChanged();
                }
            }
        }
    }

PS:什麼是WeakReference?   WeakReference弱引用,與強引用(即我們常說的引用)相對,它的特點是,GC在回收時會忽略掉弱引用,即就算有弱引用指向某物件,但只要該物件沒有被強引用指向(實際上多數時候還要求沒有軟引用,但此處軟引用的概念可以忽略),該物件就會在被GC檢查到時回收掉。對於上面的程式碼,使用者在關閉Activity之後,就算後臺執行緒還沒結束,但由於僅有一條來自Handler的弱引用指向Activity,所以GC仍然會在檢查的時候把Activity回收掉。這樣,記憶體洩露的問題就不會出現了。

總結:使用非靜態內部類(匿名內部類)時,都會隱式地持有其外部類的引用(比如:Activity),如果此時內部類的生命週期比引用的外部類的生命週期長時(比如內部類開啟子執行緒執行耗時任務),就會造成記憶體洩漏; 最常見的如Handler、Thread、AsyncTask、EventBus、RxAndroid

Android載入大圖、多圖如何避免OOM

//獲取系統分配給每個APP最高可使用的記憶體大小
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 
  • 圖片壓縮

    • 壓縮後的圖片大小應該和用來展示它的控制元件大小相近,在一個很小的ImageView上顯示一張超大的圖片不會帶來任何視覺上的好處,但卻會佔用我們相當多寶貴的記憶體,而且在效能上還可能會帶來負面影響

    • BitmapFactory這個類提供了多個解析方法(decodeByteArray, decodeFile, decodeResource等)用於建立Bitmap物件,我們應該根據圖片的來源選擇合適的方法。比如SD卡中的圖片可以使用decodeFile方法,網路上的圖片可以使用decodeStream方法,資原始檔中的圖片可以使用decodeResource方法

    • BitmapFactory提供了一個可選的BitmapFactory.Options引數,nJustDecodeBounds屬性設定為true時系統不會真正的給bitmap分配記憶體,但是BitmapFactory.Options的outWidth、outHeight和outMimeType屬性都會被賦值,但是需要注意壓縮完成圖片後需要將nJustDecodeBounds設定回false,這樣才能獲取到bitmap

      BitmapFactory.Options options = new BitmapFactory.Options();
      //設定BitmapFactory.decodeResource不分配記憶體
      options.inJustDecodeBounds = true; 
      //這個方法其實是有返回值的就是Bitmap,只是由於設定了inJustDecodeBounds=true,所以這時的返回值是null;
      BitmapFactory.decodeResource(getResources(), R.id.myimage, options); 
      //BitmapFactory.Options的outWidth、outHeight和outMimeType屬性都會被賦值
      int imageHeight = options.outHeight; 
      int imageWidth = options.outWidth; 
      String imageType = options.outMimeType;
      
    • 圖片壓縮的比例是通過BitmapFactory.Options的inSampleSize來設定的,比如我們有一張2048*1536畫素的圖片,將inSampleSize的值設定為4,就可以把這張圖片壓縮成512*384畫素,由於Height和Width都壓縮了4倍 ,那麼壓縮後圖片的記憶體大小是原來的1/16,

    • 那麼inSampleSize的值設定多少才合適呢?下面提供一種比較常用的方法:

      /**
       * @param options
       * @param reqWidth -- 目標ImageView的Width
       * @param reqHeight -- 目標ImageView的Height
       * @return
       */
      public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { 
          // 源圖片的高度和寬度 
          final int height = options.outHeight; 
          final int width = options.outWidth; 
          int inSampleSize = 1; 
          if (height > reqHeight || width > reqWidth) { 
              // 計算出實際寬高和目標寬高的比率 
              final int heightRatio = Math.round((float) height / (float) reqHeight); 
              final int widthRatio = Math.round((float) width / (float) reqWidth); 
              // 選擇寬和高中最小的比率作為inSampleSize的值,這樣可以保證最終圖片的寬和高 
              // 一定都會大於等於目標的寬和高。 
              inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; 
          } 
          return inSampleSize; 
      } 
      
      
  • 圖片快取技術

    • 記憶體快取

    對那些大量佔用應用程式寶貴記憶體的圖片提供了快速訪問的方法。其中最核心的類是LruCache (此類在android-support-v4的包中提供) 。這個類非常適合用來快取圖片,它的主要演算法原理是把最近使用的物件用強引用儲存在 LinkedHashMap 中,並且把最近最少使用的物件在快取值達到預設定值之前從記憶體中移除。 下面是一個使用 LruCache 來快取圖片的例子:

    主要步驟:
     1、要先設定快取圖片的記憶體大小,我這裡設定為手機記憶體的1/8;
     2、LruCache裡面的鍵值對分別是URL和對應的圖片;
     3、重寫了一個叫做sizeOf的方法,返回的是圖片數量;
    private LruCache<String, Bitmap> mMemoryCache; 
    
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        // 獲取到可用記憶體的最大值,使用記憶體超出這個值會引起OutOfMemory異常。 
        // LruCache通過建構函式傳入快取值,以KB為單位。 
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 
        // 使用最大可用記憶體值的1/8作為快取的大小。快取大小可以自己確定
        int cacheSize = maxMemory / 8; 
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
            @Override 
            protected int sizeOf(String key, Bitmap bitmap) { 
                // 重寫此方法來衡量每張圖片的大小,預設返回圖片數量。 
                return bitmap.getByteCount() / 1024; 
            } 
        }; 
    } 
     
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
        if (getBitmapFromMemCache(key) == null) { 
            mMemoryCache.put(key, bitmap); 
        } 
    } 
     
    public Bitmap getBitmapFromMemCache(String key) { 
        return mMemoryCache.get(key); 
    }
    
    
    • 磁碟快取

    磁碟快取沒什麼好說的直接將圖片儲存到SDCard上即可;

  • 軟引用

    只要有足夠的記憶體,就一直保持物件,直到發現記憶體吃緊且沒有Strong Ref時才回收物件。

    private Map<String, SoftReference<Bitmap>> imageMap  = new HashMap<String, SoftReference<Bitmap>>();
    獲取:
    SoftReference<Bitmap> reference = imageMap.get(key);
    Bitmap bitmap = reference.get();
    

兩者的比較說到這裡,有必要來進行一下比較了。網上有很多人使用軟引用載入圖片的多 ,但是現在已經不再推薦使用這種方式了,(1)因為從 Android 2.3 (API Level 9)開始,垃圾回收器會更傾向於回收持有軟引用或弱引用的物件, 這讓軟引用和弱引用變得不再可靠。(2)另外,Android 3.0 (API Level 11)中,圖片的資料會儲存在本地的記憶體當中, 因而無法用一種可預見的方式將其釋放,這就有潛在的風險造成應用程式的記憶體溢位並崩潰,所以我這裡用得是LruCache來快取圖片,當儲存Image的大小大於LruCache設定的值,系統自動釋放記憶體, 這個類是3.1版本中提供的,如果你是在更早的Android版本中開發,則需要匯入android-support-v4的jar包。

載入超級巨大的圖片方案(不能壓縮)

  • BitmapRegionDecoder:主要用於顯示圖片的某一塊矩形區域,如果你需要顯示某個圖片的指定區域,那麼這個類非常合適。

    • BitmapRegionDecoder提供了一系列的newInstance方法來構造物件,支援傳入檔案路徑,檔案描述符,檔案的inputstrem等
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
  • 上述解決了傳入我們需要處理的圖片,那麼接下來就是顯示指定的區域
bitmapRegionDecoder.decodeRegion(rect, options);
引數一很明顯是一個rect,引數二是BitmapFactory.Options,你可以控制圖片的inSampleSize,inPreferredConfig等

設計模式

  • 建立型

    • 單例模式—Applica