1. 程式人生 > >Android面試之高階篇

Android面試之高階篇

結合自己之前去很多大公司的面試經歷和自己面別人的一些題,這裡做一些總結,Android面試中常見的面試題。

1,Android的Handler執行機制

    要解釋Handler的執行機制就要講幾個物件:Message、Handler、Message Queue、Looper。Handler獲取當前執行緒中的looper物件,looper用來從存放Message的   MessageQueue中取出Message,再有Handler進行Message的分發和處理。

Message Queue(訊息佇列):用來存放通過Handler釋出的訊息,通常附屬於某一個建立它的執行緒,可以通過Looper.myQueue()得到當前執行緒的訊息佇列
Handler:可以釋出或者處理一個訊息或者操作一個Runnable,通過Handler釋出訊息,訊息將只會傳送到與它關聯的訊息佇列,然也只能處理該訊息佇列中的訊息
Looper:是Handler和訊息佇列之間通訊橋樑,程式元件首先通過Handler把訊息傳遞給Looper,Looper把訊息放入佇列。Looper也把訊息佇列裡的訊息廣播給所有的
Handler:Handler接受到訊息後呼叫handleMessage進行處理
Message:訊息的型別,在Handler類中的handleMessage方法中得到單個的訊息進行處理
在單執行緒模型下,為了執行緒通訊問題,Android設計了一個Message Queue(訊息佇列), 執行緒間可以通過該Message Queue並結合Handler和Looper元件進行資訊交換。下面將對它們進行分別介紹:

1. Message
    Message訊息,理解為執行緒間交流的資訊,處理資料後臺執行緒需要更新UI,則傳送Message內含一些資料給UI執行緒。
2. Handler
    Handler處理者,是Message的主要處理者,負責Message的傳送,Message內容的執行處理。後臺執行緒就是通過傳進來的 Handler物件引用來sendMessage(Message)。而使用Handler,需要implement 該類的 handleMessage(Message)方法,它是處理這些Message的操作內容,例如Update UI。通常需要子類化Handler來實現handleMessage方法。
3. Message Queue
    Message Queue訊息佇列,用來存放通過Handler釋出的訊息,按照先進先出執行。
    每個message queue都會有一個對應的Handler。Handler會向message queue通過兩種方法傳送訊息:sendMessage或post。這兩種訊息都會插在message queue隊尾並按先進先出執行。但通過這兩種方法傳送的訊息執行的方式略有不同:通過sendMessage傳送的是一個message物件,會被 Handler的handleMessage()函式處理;而通過post方法傳送的是一個runnable物件,則會自己執行。
4. Looper
    Looper是每條執行緒裡的Message Queue的管家。Android沒有Global的Message Queue,而Android會自動替主執行緒(UI執行緒)建立Message Queue,但在子執行緒裡並沒有建立Message Queue。所以呼叫Looper.getMainLooper()得到的主執行緒的Looper不為NULL,但呼叫Looper.myLooper() 得到當前執行緒的Looper就有可能為NULL。對於子執行緒使用Looper,API Doc提供了正確的使用方法:這個Message機制的大概流程:
    1. 在Looper.loop()方法執行開始後,迴圈地按照接收順序取出Message Queue裡面的非NULL的Message。
    2. 一開始Message Queue裡面的Message都是NULL的。當Handler.sendMessage(Message)到Message Queue,該函式裡面設定了那個Message物件的target屬性是當前的Handler物件。隨後Looper取出了那個Message,則呼叫 該Message的target指向的Hander的dispatchMessage函式對Message進行處理。在dispatchMessage方法裡,如何處理Message則由使用者指定,三個判斷,優先順序從高到低:
    1) Message裡面的Callback,一個實現了Runnable介面的物件,其中run函式做處理工作;
    2) Handler裡面的mCallback指向的一個實現了Callback介面的物件,由其handleMessage進行處理;
    3) 處理訊息Handler物件對應的類繼承並實現了其中handleMessage函式,通過這個實現的handleMessage函式處理訊息。
    由此可見,我們實現的handleMessage方法是優先順序最低的!
    3. Handler處理完該Message (update UI) 後,Looper則設定該Message為NULL,以便回收!
    在網上有很多文章講述主執行緒和其他子執行緒如何互動,傳送資訊,最終誰來執行處理資訊之類的,個人理解是最簡單的方法——判斷Handler物件裡面的Looper物件是屬於哪條執行緒的,則由該執行緒來執行!
    1. 當Handler物件的建構函式的引數為空,則為當前所線上程的Looper;

2,Android的Activity的四種啟動模式和用途

standerd

預設模式,可以不用寫配置。在這個模式下,都會預設建立一個新的例項。因此,在這種模式下,可以有多個相同的例項,也允許多個相同Activity疊加。應用場景:絕大多數Activity。

singleTop

棧頂複用模式,如果要開啟的activity在任務棧的頂部已經存在,就不會建立新的例項,而是呼叫 onNewIntent() 方法。避免棧頂的activity被重複的建立。

singleInstance

“singleInstance”獨佔一個task,其它activity不能存在那個task裡;如果它啟動了一個新的activity,不管新的activity的launch mode 如何,新的activity都將會到別的task裡執行(如同加了FLAG_ACTIVITY_NEW_TASK引數)。

singleTask

棧內複用模式, activity只會在任務棧裡面存在一個例項。如果要啟用的activity,在任務棧裡面已經存在,就不會建立新的activity,而是複用這個已經存在的activity,呼叫 onNewIntent() 方法,並且清空這個activity任務棧上面所有的activity。

3,解釋一下activity、 intent 、intent filter、service、Broadcase、BroadcaseReceiver

一個activity呈現了一個使用者可以操作的視覺化使用者介面;一個service不包含可見的使用者介面,而是在後臺執行,可以與一個activity繫結,通過繫結暴露出來介面並與其進行通訊;一個broadcast receiver是一個接收廣播訊息並做出迴應的component,broadcast receiver沒有介面;一個intent是一個Intent物件,它儲存了訊息的內容。對於activity和service來說,它指定了請求的操作名稱和待操作資料的URI,Intent物件可以顯式的指定一個目標component。如果這樣的話,android會找到這個component(基於manifest檔案中的宣告)並激活它。但如果一個目標不是顯式指定的,android必須找到響應intent的最佳component。它是通過將Intent物件和目標的intent filter相比較來完成這一工作的;一個component的intent filter告訴android該component能處理的intent。intent filter也是在manifest檔案中宣告的。

4,、Serializable和Parcelable的區別

在使用記憶體的時候,Parcelable 類比Serializable效能高,所以推薦使用Parcelable類。
1.Serializable在序列化的時候會產生大量的臨時變數,從而引起頻繁的GC。
傳智播客武漢校區就業部出品 務實、創新、質量、分享、專注、責任
32
2.Parcelable不能使用在要將資料儲存在磁碟上的情況。儘管Serializable效率低點,但在這
種情況下,還是建議你用Serializable 。
實現:
1.Serializable 的實現,只需要繼承 Serializable 即可。這只是給物件打了一個標記,系統會
自動將其序列化。
2.Parcelabel 的實現,需要在類中新增一個靜態成員變數 CREATOR,這個變數需要繼承
Parcelable.Creator 介面。

[html] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. <spanstyle="font-size:14px;">public class MyParcelable implements Parcelable {  
  2.     private int mData;  
  3.     public int describeContents() {  
  4.         return 0;  
  5.     }  
  6.     public void writeToParcel(Parcel out, int flags) {  
  7.         out.writeInt(mData);  
  8.     }  
  9.     public static final Parcelable.Creator<MyParcelable>CREATOR = new Parcelable.Creator<MyParcelable>() {  
  10.         public MyParcelable createFromParcel(Parcel in) {  
  11.             return new MyParcelable(in);  
  12.         }  
  13.         public MyParcelable[] newArray(int size) {  
  14.             return new MyParcelable[size];  
  15.         }  
  16.     };  
  17.     private MyParcelable(Parcel in) {  
  18.         mData = in.readInt();  
  19.     }  
  20. }</span>
public class MyParcelable implements Parcelable {
	private int mData;

	public int describeContents() {
		return 0;
	}

	public void writeToParcel(Parcel out, int flags) {
		out.writeInt(mData);
	}

	public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() {
		public MyParcelable createFromParcel(Parcel in) {
			return new MyParcelable(in);
		}

		public MyParcelable[] newArray(int size) {
			return new MyParcelable[size];
		}
	};

	private MyParcelable(Parcel in) {
		mData = in.readInt();
	}
}

5,什麼是記憶體洩漏,android在什麼情況下容易產生記憶體洩漏

說到記憶體洩漏就不得不提記憶體溢位。

記憶體溢位 out of memory,是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out of memory;比如申請了一個integer,但給它存了long才能存下的數,那就是記憶體溢位。

記憶體洩露 memory leak,是指程式在申請記憶體後,無法釋放已申請的記憶體空間,一次記憶體洩露危害可以忽略,但記憶體洩露堆積後果很嚴重。記憶體溢位導致了記憶體洩漏。

在Android中常見的記憶體洩漏原因:

1. 資源釋放問題
程式程式碼的問題,長期保持某些資源,如Context、Cursor、IO流的引用,資源得不到釋放
造成記憶體洩露。
2. 物件記憶體過大問題
儲存了多個耗用記憶體過大的物件(如Bitmap、XML檔案),造成記憶體超出限制。
3. static關鍵字的使用問題
static是Java中的一個關鍵字,當用它來修飾成員變數時,那麼該變數就屬於該類,而不是
該類的例項。所以用static修飾的變數,它的生命週期是很長的,如果用它來引用一些資源耗費
過多的例項(Context的情況最多),這時就要謹慎對待了。
public class ClassName {
private static Context mContext;
//省略
}
以上的程式碼是很危險的,如果將 Activity 賦值到 mContext 的話。那麼即使該 Activity 已經
onDestroy,但是由於仍有物件儲存它的引用,因此該Activity依然不會被釋放。

4. 執行緒導致記憶體溢位
執行緒產生記憶體洩露的主要原因在於執行緒生命週期的不可控。

5,資料庫遊標忘記回收等

那麼針對上面的問題我們怎麼避免呢

1、圖片過大導致OOM
Android 中用bitmap時很容易記憶體溢位,比如報如下錯誤:Java.lang.OutOfMemoryError :
bitmap size exceeds VM budget。
解決方法:
方法1: 等比例縮小圖片

[html] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. BitmapFactory.Options options = new BitmapFactory.Options();   
  2. options.inSampleSize = 2; //Options 只儲存圖片尺寸大小,不儲存圖片到記憶體   
  3. BitmapFactory.Options opts = new BitmapFactory.Options();   
  4. opts.inSampleSize = 2;   
  5. Bitmap bmp = null;   
  6. bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts);   
  7. //回收  
  8. bmp.recycle();//  
BitmapFactory.Options options = new BitmapFactory.Options(); 
options.inSampleSize = 2; //Options 只儲存圖片尺寸大小,不儲存圖片到記憶體 
BitmapFactory.Options opts = new BitmapFactory.Options(); 
opts.inSampleSize = 2; 
Bitmap bmp = null; 
bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts); 
//回收
bmp.recycle();//
方法2:對圖片採用軟引用,及時地進行recyle()操作 [html] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. SoftReference<Bitmap>bitmap = new SoftReference<Bitmap>(pBitmap);   
  2. if(bitmap != null){   
  3.     if(bitmap.get() != null && !bitmap.get().isRecycled()){   
  4.         bitmap.get().recycle();   
  5.         bitmap = null; }   
  6.     }  
SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap); 
if(bitmap != null){ 
	if(bitmap.get() != null && !bitmap.get().isRecycled()){ 
		bitmap.get().recycle(); 
		bitmap = null; } 
	}

2、查詢資料庫沒有關閉遊標
程式中經常會進行查詢資料庫的操作,但是經常會有使用完畢Cursor後沒有關閉的情況。如果
我們的查詢結果集比較小,對記憶體的消耗不容易被發現,只有在常時間大量操作的情況下才會出現內
存問題,這樣就會給以後的測試和問題排查帶來困難和風險。

3、構造Adapter時,沒有使用快取的 convertView
在使用ListView的時候通常會使用Adapter,那麼我們應該儘可能的使用ConvertView。
為什麼要使用convertView?
當convertView為空時,用setTag()方法為每個View繫結一個存放控制元件的ViewHolder物件。
當 convertView 不為空,重複利用已經建立的 view 的時候,使用 getTag()方法獲取繫結的
ViewHolder物件,這樣就避免了findViewById對控制元件的層層查詢,而是快速定位到控制元件。
4、Bitmap物件不再使用時呼叫recycle()釋放記憶體
有時我們會手工的操作Bitmap物件,如果一個Bitmap物件比較佔記憶體,當它不再被使用的時

候,可以呼叫Bitmap.recycle()方法回收此物件的畫素所佔用的記憶體,但這不是必須的,視情況而定。

6、 簡述下Android JNI呼叫過程
1)安裝和下載Cygwin,下載AndroidNDK
2)在ndk專案中JNI介面的設計
3)使用C/C++實現本地方法
4)JNI生成動態連結庫.so檔案
5)將動態連結庫複製到java工程,在java工程中呼叫,執行java工程即可

以上就列舉這麼多了,其他的大家可以自行搜尋。