天天看點

Android面試總結--Android篇

相關文章:Android面試總結–Java篇

Activity生命周期

Android面試總結--Android篇

圖中需要注意一下幾點:

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,不會回調。

Android面試總結--Android篇

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生命周期

Android面試總結--Android篇
  • 被啟動的服務的生命周期:如果一個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中常見的線程池有四種,FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor。

  • 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,用戶端是會請求失敗的,是以我們可以在這個方法中做一些安全驗證。
Android面試總結--Android篇

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

  • 1、當用戶端發起請求時,由于目前線程會被挂起,直到服務端傳回資料,如果這個遠端方法很耗時的話,那麼是不能夠在UI線程,也就是主線程中發起這個遠端請求的。
  • 2、由于Service的Binder方法運作線上程池中,是以Binder方法不管是耗時還是不耗時都應該采用同步的方式,因為它已經運作在一個線程中了。

Binder機制具體有兩層含義:

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

Android跨程序通信AIDL詳解

Android-IPC之Binder機制簡介

另一種了解:

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流程圖:

Android面試總結--Android篇

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的生命周期一樣

    • Handler、AnycTask、Thread、Runable使用引起的記憶體洩漏
      Android使用Handler造成記憶體洩露的分析及解決方法
    • 非靜态内部類或者匿名内部類都有隐式持有外部類(通常是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
Android面試總結--Android篇

ContentProvider優缺點:

優點:

  • 為資料通路提供統一的接口
  • 跨程序通路

缺點:

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

原理:

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

URI定義

Android面試總結--Android篇
  • 主題名Schema:URI格式的字首(固定);
  • 授權資訊(Authority):唯一辨別符
  • 表名(Path):資料庫的表名
  • 記錄(ID):表中指定的某條記錄(不指定則傳回全部)

深入了解Android之View的繪制流程

BroadcastReceiver

  • 四大元件之一,(唯一一個可以不用再Mainfest注冊的,因為它支援動态注冊)
  • 動态注冊和靜态注冊
    Android面試總結--Android篇

靜态注冊在App首次啟動時,系統會自動執行個體化mBroadcastReceiver類,并注冊到系統中

原理:

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

BroadcastReceiver模型中有3個角色:

  • 消息訂閱者(廣播接收者)
  • 消息釋出者(廣播釋出者)
  • 消息中心(AMS,即Activity Manager Service)
Android面試總結--Android篇

生命周期

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 onTouch事件傳遞機制

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

Android熱修複原理

Android 熱修複原理篇及幾大方案比較

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等
           

設計模式

《Android源碼設計模式解析》讀書筆記——Android中你應該知道的設計模式

設計模式在Android源碼中的運用

  • 建立型
    • 單例模式—Application、LayoutInflater、WindowsManagerService、ActivityManagerService
    • Builder模式—AlertDialog.Builder、StringBuilder、StringBuffer、Notification
    • 原型模式—Intent
    • 工廠方法模式—Activity的各種生命周期、ArrayList和HashSet
    • 抽象工廠模式—MediaPlayer
  • 行為型
    • 政策模式—動畫插值器LinearInterpolator
    • 狀态模式—Wi-Fi管理
    • 責任鍊模式—View事件的分發處理
    • 解釋器模式—PackageParser
    • 指令模式—PackageHandler
    • 觀察者模式—BaseAdapter、EventBus、Otto、AndroidEventBus
    • 備忘錄模式—onSaveInstanceState和onRestoreInstanceState
    • 疊代器模式—Cursor
    • 模闆方法模式—AsyncTask;Activity的生命周期
    • 通路者模式—ButterKnife、Dagger、Retrofit
    • 中介者模式—Keyguard解鎖屏
  • 結構型
    • 代理模式—ActivityManagerProxy代理類
    • 組合模式—View和ViewGroup的嵌套組合
    • 擴充卡模式—ListView、GridView、RecyclerView
    • 裝飾模式—Stream
    • 享元模式—String
    • 外觀模式—Context
    • 橋接模式—Window與WindowManager

工廠方法模式和抽象工廠模式差別

簡單工廠模式

工廠方法模式

抽象工廠模式

相關文章:Android面試總結–Java篇