1. 程式人生 > >關於Android面試的一些常問知識點及解答

關於Android面試的一些常問知識點及解答

關於這十來天(7月17日-7月27日)的一些Android面試知識點

相比較去年中旬,這階段安卓的崗位需求量確實比較大,基本上都是招聘者找上門來,所以做好一些基本的準備,鞏固好知識點,是很有機會的。下面是一些經常被問到的問題:

1.在子執行緒中能不能直接new Handler()

答:不能。如果在子執行緒中直接new Handler(),會丟擲異常java.lang.RuntimeException:Can't create handler inside thread that has not called Looper.prepare();因為在Hanlder的構造方法中,會先檢查有沒有建立訊息迴圈,如若沒有,則會報錯,所以需要先進行Looper.prepare()啟動訊息迴圈。

2.講講Handler機制

答:Looper是用來封裝訊息迴圈和訊息佇列的一個類,而handler可以看成一個用來向訊息佇列插入訊息的工具類,一個handler執行緒擁有一個looper物件,對應一個MessageQueue訊息佇列。在主執行緒中,會自動為其建立Looper物件,並開啟訊息迴圈,handler通過繫結的Looper物件向訊息佇列插入訊息,looper.looper()方法是一個死迴圈,不斷從訊息佇列中取訊息,如果有訊息就讀取,否則就阻塞。

3.談談自定義view的過程

①通過繼承原生view控制元件。比如可以去繼承一個Linearlayout,然後用LayoutInflater去載入xml檔案,再去呼叫具體的方法;

②繼承View:先建立一個屬性檔案res/values/styles.xml,宣告一個自定義屬性的集合,然後在構造方法通過TypeArray載入自定義屬性集合,並跟屬性名去設定數值。

關鍵的方法:onMeasure(int widthMeasureSpec,int heightMeasureSpec),對當前View進行尺寸的測量,其中widthMeasureSpec和heightMeasureSpec兩個引數包含了寬和高的資訊,以及測量模式,

widthMeasureSpec、heightMeasureSpec,int整型資料佔用32bit位元組,而google將前面的2個bit用於區分不同的佈局模式,後面的30個bit存放的是尺寸的資料。

測量模式:UNSPECIFIED 父容器沒有對當前view做任何限制,當前view可以取任何尺寸

EXACITY 當前的尺寸就是view的尺寸

AT_MOST 當前的尺寸是view的最大尺寸

程式碼:

int widthSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);

int heightSpec = MeasureSpec.makeMeasureSpec(200,MeasureSpec.AT_MOST);

view.measure(widthSpec,height);

例:自定義流式佈局flowlayout:

先寫一個view自定義ViewGroup;

在onMeasure拿到ViewGroup的尺寸和測量模式:

需要遍歷子控制元件,根據相同方法拿到每個view的尺寸和測量模式,然後進行測量:

①計算每一行已使用的寬度,如果當前行的子view的寬度加上下一個view的寬度小於父容器的總寬度,則將下一個view加入行中。否則換行新增view操作。

②換行新增view到行中,有兩種情況,一種是當前行中沒有其他元素,單個view尺寸超過父控制元件,則該view單獨一行加入,而後進行換行操作;另一種是當前行已經有元素,則需要新建一行,將view加進去。

③寬度設定好後,對高度進行測量計算,高度以當前行的子控制元件的最大高度為準

④onLayout() 進行佈局,因為先前已經得到測量尺寸,只需在此設定。

4.android事件傳遞機制

事件傳遞的方法:父控制元件→子控制元件

事件響應的方法:子控制元件→父控制元件

在ViewGroup中事件有dispatchTouchEvent() onInterceptTouchEvent() onTouchEvent()

在View中事件有 dispatchTouchEvent onTouchEvent()

①dispatchTouchEvent()最先呼叫,只負責分發,dispatchTouchEvent()首先接收到ACTION_DOWN,執行super.dispatchTouchEvent(ev),事件向下分發。dispatchTouchEvent()返回true,後續事件(ACTION_MOVE、ACTION_UP)會再傳遞,如果返回false,dispatchTouchEvent()就接收不到ACTION_UP、ACTION_MOVE。

分發事件一直都存在。

②onInterceptTouchEvent()攔截事件,如果返回true,在onTouchListener處理,否則向下傳遞。

③onTouchListener() 消費,返回true,在本View處理事件,返回false,向上傳遞。

5.談談mvc和mvp

mvc是軟體架構中比較常見的一種框架,通過controller的控制去操作model層的資料,並且返回給view層顯示,反過來說,當用戶發出事件時,view層會發送指令給controller層,controller又會去通知model層進行資料更新,然後直接顯示在view層上。比如說你動態的去展示一個button按鈕的隱藏和顯示,這些方法沒有直接寫在xml中,必須在activity中進行編碼,從而造成activity既是view層也是controller層。

mvp的話,就是對mvc上訴的缺點進行改進,所有有關使用者事件的轉發都放在presenter層進行處理,相當於一箇中間橋樑,view層的事件傳到presenter層,presenter層去操作model層資料,並返回給view層展示,整個過程中view層和model層沒有直接互動,就是一個解耦的過程。需要根據所需的邏輯進行介面化的定義,維護介面的成本會增加。

6.記憶體洩露

①單例設計模式造成的記憶體洩漏

public class AppManager {

private static AppManager instance;

private Context context;

private AppManager(Context context) {

this.context = context;

}

public static AppManager getInstance(Context context) {

if (instance != null) {

instance = new AppManager(context);

}

return instance;

}

}

1、如果我們傳入的Context是Application的Context的話,就沒有任何問題,因為Application的Context生命週期和應用程式生命週期一樣長。

2、如果我們傳入的Context是Activity的Context的話,這時如果我們因為需求銷燬了該Activity的話,Context也會隨著Activity被銷燬,但是單例還在持有對該類物件的引用,這時就會造成記憶體洩漏。

public class AppManager {

private static AppManager instance;

private Context context;

private AppManager(Context context) {

this.context = context.getApplicationContext();

}

public static AppManager getInstance(Context context) {

if (instance != null) {

instance = new AppManager(context);

}

return instance;

}

}

②非靜態內部類建立的靜態例項造成的記憶體洩漏

public class MainActivity extends AppCompatActivity {

private static TestResource mResource = null;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

if(mManager == null){

mManager = new TestResource();

}

//...

}

class TestResource {

//...

}

在Activity中建立了非靜態內部類,非靜態內部類預設持有Activity類的引用,但是他的生命週期還是和應用程式一樣長,所以當Activity銷燬時,靜態內部類的物件引用不會被GC回收,就會造成了記憶體溢位,

解決辦法:

1、將內部類改為靜態內部類。

2、將這個內部類封裝成一個單例,Context使用Application的Context

③Handler造成的記憶體洩漏

public class MainActivity extends AppCompatActivity {

private Handler mHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

//...

}

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

loadData();

}

private void loadData(){

//...request

Message message = Message.obtain();

mHandler.sendMessage(message);

}

handler也是一個非靜態匿名內部類,他跟上面的一樣,也會持有Activity的引用,我們知道handler是執行在一個Looper執行緒中的,而Looper執行緒是輪詢來處理訊息佇列中的訊息的,假設我們處理的訊息有十條,而當他執行到第6條的時候,使用者點選了back返回鍵,銷燬了當前的Activity,這個時候訊息還沒有處理完,handler還在持有Activity的引用,這個時候就會導致無法被GC回收,造成了記憶體洩漏。正確的做法是:在onDestory呼叫

mHandler.removeCallbacksAndMessages()

④執行緒造成的記憶體洩漏

//——————test1

new AsyncTask<Void, Void, Void>() {

@Override

protected Void doInBackground(Void... params) {

SystemClock.sleep(10000);

return null;

}

}.execute();

//——————test2

new Thread(new Runnable() {

@Override

public void run() {

SystemClock.sleep(10000);

}

}).start();

上面是兩個內部類,當我們的Activity銷燬時,這兩個任務沒有執行完畢,就會使Activity的記憶體資源無法被回收,造成了記憶體洩漏。

正確的做法是使用靜態內部類:如下

static class MyAsyncTask extends AsyncTask<Void, Void, Void> {

private WeakReference<Context> weakReference;

public MyAsyncTask(Context context) {

weakReference = new WeakReference<>(context);

}

@Override

protected Void doInBackground(Void... params) {

SystemClock.sleep(10000);

return null;

}

@Override

protected void onPostExecute(Void aVoid) {

super.onPostExecute(aVoid);

MainActivity activity = (MainActivity) weakReference.get();

if (activity != null) {

//...

}

}

}

static class MyRunnable implements Runnable{

@Override

public void run() {

SystemClock.sleep(10000);

}

}

//——————

new Thread(new MyRunnable()).start();

new MyAsyncTask(this).execute();

這樣就避免了記憶體洩漏,當然在Activity銷燬時也要記得在OnDestry中呼叫AsyncTask.cancal()方法來取消相應的任務。避免在後臺執行浪費資源。

⑤資源未關閉造成的記憶體洩漏

在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源時,一定要在Activity中的OnDestry中及時的關閉、登出或者釋放記憶體,

否則這些資源不會被GC回收,就會造成記憶體洩漏。

7.效能優化

①佈局優化:如果佈局能用LinearLayout或RelativeLayout使用,使用LinearLayout,因為RelativeLayout功能比較複雜,它的佈局過程需要花費更多的CPU時間;採用include、merge標籤進行佈局的重用,ViewStub為佈局的按需載入,需要時才會將ViewStub中的佈局載入到記憶體,提高了程式初始化效率。

②記憶體洩漏優化:AndroidStudio的Android Monitor檢視記憶體使用情況,或者下載MAT工具對記憶體洩漏情況進行分析;不存在的物件進行釋放和回收;單例、靜態變數、匿名內部類、非靜態內部類、資源未關閉等造成的記憶體洩漏

③採取AsyncTask、Handler進行UI介面的更新

④ListView/RecycleView及Bitmap優化:使用ViewHolder模式來提高效率;非同步載入,將耗時任務放在非同步執行緒中;ListView/RecycleView滑動時停止載入資料;bitmap圖片的壓縮

⑤執行緒優化:採用執行緒池

⑥其它效能優化:

避免過度的建立物件;

不要過度使用列舉,

列舉佔用的記憶體空間要比整型大;

常量請使用static final來修飾;

使用一些Android特有的資料結構,比如SparseArray和Pair等;

適當採用軟引用和弱引用;

採用記憶體快取和磁碟快取;

儘量採用靜態內部類,這樣可以避免潛在的由於內部類而導致的記憶體洩漏。

8.執行緒

Thread:子執行緒,可以去執行一些耗時任務。簡單的在子執行緒執行下載操作,由於子執行緒是與UI執行緒保持簡單的同步關係,所以在子執行緒執行下載耗時任務是不安全。

AsyncTask:執行緒池的一個封裝,採用非同步任務進行操作,

執行緒池:執行緒池的優點有

(1)複用執行緒池中的執行緒,避免因為執行緒的建立和銷燬所帶來的效能開銷。

(2)能夠有效的控制執行緒池的最大併發數,避免大量的執行緒之間因互相搶佔系統資源而導致的阻塞現象。

(3)能夠對執行緒進行簡單的管理,並提供定時執行以及指定間隔迴圈執行等功能。

單例模式·懶漢式

public class Singleton {

private static Singleton instance=null;

private Singleton() {

}

public static Singleton getInstance(){

if (instance == null) {

instance = new Singleton();

}

return instance;

}

}

在單執行緒程式中,上面兩種形式基本可以滿足要求了,但是在多執行緒環境下,單例類就有可能會失效,這個時候就要對其加鎖了,來確保執行緒安全。

synchronized同步塊進行加鎖

public class Singleton {

private static Singleton instance;

private Singleton() {

}

public static Singleton getInstance(){

if (instance == null) {

synchronized (Singleton.class) {

if (instance == null) {

instance = new Singleton();

}

}

}

return instance;

}

}

9.Android分渠道打包

舉了友盟多渠道打包的例子:

1.新增友盟依賴庫

2.在Manifest.xml中加入設定友盟的key值和渠道名

3.在build.gradle根目錄下加入配置,及需要的渠道名

4.然後通過AndroidStudio的release打包時,選中渠道名,完成多渠道打包

10.Android資料加解密

Android加密演算法有多種多樣,常見的有MD5、RSA、AES、3DES四種

11.WebView與JavaScript的互動

Android去呼叫JS的程式碼:

①通過webview的loadUrl()方法 Android4.4之前用

html中寫有一個方法call(),call()裡面是一個彈窗:alert("Android呼叫了JS的call方法");

android程式碼中

WebSettings webSettings = mWebView.getSettings();

// 設定與Js互動的許可權

webSettings.setJavaScriptEnabled(true);

// 設定允許JS彈窗

webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

然後通過mWebView.loadUrl("javascript:call()");

最後設定setWebChromeClient()去響應alert()彈窗事件

②通過webview的evaluateJavascript() Android4.4後用

該方法比第一種方法更高效、更簡潔

因為該方法的執行不會重新整理頁面,而第一種方法loadUrl會

Android4.4後才用

// 只需要將第一種方法的loadUrl()換成下面該方法即可

mWebView.evaluateJavascript("javascript:call()", new ValueCallback<String>() {

@Override

public void onReceiveValue(String value) {

//此處為 js 返回的結果

}

});

}

JS去呼叫Android的程式碼:

①通過WebView的addJavascriptInterface()進行物件對映

步驟1:定義一個與JS物件對映關係的Android類:AndroidtoJs

// 繼承自Object類

public class AndroidtoJs extends Object {

// 定義JS需要呼叫的方法

// 被JS呼叫的方法必須加入@JavascriptInterface註解

@JavascriptInterface

public void hello(String msg) {

System.out.println("JS呼叫了Android的hello方法");

}

}

步驟2:將需要呼叫的JS程式碼以.html格式放到src/main/assets資料夾裡

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<title>Carson</title>

<script>

function callAndroid(){

// 由於物件對映,所以呼叫test物件等於呼叫Android對映的物件

test.hello("js呼叫了android中的hello方法");

}

</script>

</head>

<body>

//點選按鈕則呼叫callAndroid函式

<button type="button" id="button1" onclick="callAndroid()"></button>

</body>

</html>

步驟3:在Android裡通過WebView設定Android類與JS程式碼的對映

// 設定與Js互動的許可權

webSettings.setJavaScriptEnabled(true);

// 通過addJavascriptInterface()將Java物件對映到JS物件

//引數1:Javascript物件名

//引數2:Java物件名

mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS類物件對映到js的test物件

// 載入JS程式碼

// 格式規定為:file:///android_asset/檔名.html

mWebView.loadUrl("file:///android_asset/javascript.html");

②通過WebViewClient的shouldOverrideUrlLoading()方法回撥攔截url

③通過 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回撥攔截JS對話方塊alert()、confirm()、prompt() 訊息

12.Android適配

①dp

②AutoLayout庫

③LinearLayout、RelativeLayout

④適配手機的單面板(預設)佈局:res/layout/main.xml

適配尺寸>7寸平板的雙面板佈局(Android 3.2前):res/layout-large/main.xml

適配尺寸>7寸平板的雙面板佈局(Android 3.2後)res/layout-sw600dp/main.xml

④使用"wrap_content"、"match_parent"和"weight“來控制檢視元件的寬度和高度

⑤資源圖片可以使用.9.png字尾名的圖片

⑥ImageView設定ScaleType為CenterCrop

13.ScrollView巢狀RecycleView出現的解決方案

①利用RecyclerView內部方法

recyclerView.setHasFixedSize(true);

recyclerView.setNestedScrollingEnabled(false);

setHasFixedSize(true)方法使得RecyclerView能夠固定自身size不受adapter變化的影響

setNestedScrollingeEnabled(false)方法則是進一步呼叫了RecyclerView內部NestedScrollingChildHelper物件的setNestedScrollingeEnabled(false)方法,如下

public void setNestedScrollingEnabled(boolean enabled) {

getScrollingChildHelper().setNestedScrollingEnabled(enabled);

}

進而,NestedScrollingChildHelper物件通過該方法關閉RecyclerView的巢狀滑動特性,如下

public void setNestedScrollingEnabled(boolean enabled) {

if (mIsNestedScrollingEnabled) {

ViewCompat.stopNestedScroll(mView);

}

mIsNestedScrollingEnabled = enabled;

}

如此一來,限制了RecyclerView自身的滑動,整個頁面滑動僅依靠ScrollView實現,即可解決滑動卡頓的問題

②通過重寫ScrollView的onInterceptTouchEvent(MotionEvent ev)方法,攔截滑動事件,使得滑動事件能夠直接傳遞給RecyclerView

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN:

// 儲存當前touch的縱座標值

touch = (int) ev.getRawY();

break;

case MotionEvent.ACTION_MOVE:

// 滑動距離大於slop值時,返回true

if (Math.abs((int) ev.getRawY() - touch) > slop) return true;

break;

}

return super.onInterceptTouchEvent(ev);

}

/**

* 獲取相應context的touch slop值(即在使用者滑動之前,能夠滑動的以畫素為單位的距離)

* @param context ScrollView對應的context

*/

private void setSlop(Context context) {

slop = ViewConfiguration.get(context).getScaledTouchSlop();

}

14.EventBus

EventBus能夠簡化各元件間的通訊,讓我們的程式碼書寫變得簡單,能有效的分離事件傳送方和接收方(也就是解耦的意思),能避免複雜和容易出錯的依賴性和生命週期問題。

15.資料庫sqlite

MySQL對查詢結果排序,從表中查詢出來的資料,可能是無序的,或者其排列順序表示使用者期望的。

使用ORDER BY對查詢結果進行排序

SELECT 欄位名1,欄位名2,……

FROM 表名

ORDER BY 欄位名1 [ASC|DESC],欄位名2[ASC|DESC]

學生表按分數降序排列:

select * from Student order by score desc

16.Android最新技術

有些面試官也會問一些Android最新的技術,比如谷歌大會發布了哪些內容等等