關於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最新的技術,比如谷歌大會發布了哪些內容等等