1. 程式人生 > >2018年Android面試遇到的問題整理

2018年Android面試遇到的問題整理

備註:以下問題的答案均是個人整理的,如有不同意見,歡迎斧正。

1.自定義Handler時如何避免記憶體洩漏

答案:

一般非靜態內部類持有外部類的引用的情況下,造成外部類在使用完成後不能被系統回收記憶體,從而造成記憶體洩漏。為了避免這個問題,我們可以自定義的Handler宣告為靜態內部類形式,然後通過弱引用的方式,讓Handler持有外部類的引用,從而可避免記憶體洩漏問題。

以下是程式碼實現

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private TextView mTextView;

    private WeakReference<MainActivity> activityWeakReference;
    private MyHandler myHandler;

    static class MyHandler extends Handler {
        private MainActivity activity;

        MyHandler(WeakReference<MainActivity> ref) {
            this.activity = ref.get();
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    //需要做判空操作
                    if (activity != null) {
                        activity.mTextView.setText("new Value");
                    }
                    break;
                default:
                    Log.i(TAG, "handleMessage: default ");
                    break;
            }


        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //在onCreate中初始化
        activityWeakReference = new WeakReference<MainActivity>(this);
        myHandler = new MyHandler(activityWeakReference);

        myHandler.sendEmptyMessage(1);
        mTextView = (TextView) findViewById(R.id.tv_test);
    }
}

參考博文http://blog.csdn.net/ucxiii/article/details/50972747

2.onNewIntent()的呼叫時機

解析:

在Android應用程式開發的時候,從一個Activity啟動另一個Activity並傳遞一些資料到新的Activity上非常簡單,但是當您需要讓後臺執行的Activity回到前臺並傳遞一些資料可能就會存在一點點小問題。

首先,在預設情況下,當您通過Intent啟到一個Activity的時候,就算已經存在一個相同的正在執行的Activity,系統都會建立一個新的Activity例項並顯示出來。為了不讓Activity例項化多次,我們需要通過在AndroidManifest.xml配置activity的載入方式(launchMode)以實現單任務模式,如下所示:

<activity android:label="@string/app_name" android:launchmode="singleTask"android:name="Activity1"></activity>

launchMode為singleTask的時候,通過Intent啟到一個Activity,如果系統已經存在一個例項,系統就會將請求傳送到這個例項上,但這個時候,系統就不會再呼叫通常情況下我們處理請求資料的onCreate方法,而是呼叫onNewIntent方法

答案:

前提:ActivityA已經啟動過,處於當前應用的Activity堆疊中; 
當ActivityA的LaunchMode為SingleTop時,如果ActivityA在棧頂,且現在要再啟動ActivityA,這時會呼叫onNewIntent()方法 
當ActivityA的LaunchMode為SingleInstance,SingleTask時,如果已經ActivityA已經在堆疊中,那麼此時會呼叫onNewIntent()方法 

當ActivityA的LaunchMode為Standard時,由於每次啟動ActivityA都是啟動新的例項,和原來啟動的沒關係,所以不會呼叫原來ActivityA的onNewIntent方法,仍然呼叫的是onCreate方法

以下是程式碼例項

1.設定MainActivity的啟動模式為SingleTask(棧內複用)

 <activity
            android:name=".MainActivity"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

2.MainActivity中重寫onNewIntent方法

package code.xzy.com.handlerdemo;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.forward_btn);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(MainActivity.this, Main2Activity.class));
            }
        });

    }

    @Override
    protected void onNewIntent(Intent intent) {
        Toast.makeText(this, "onnewIntent", Toast.LENGTH_SHORT).show();
        Log.i(TAG, "onNewIntent: i done....");
    }
}

3.Main2Actvity執行點選跳轉,MainActivity被複用,執行onNewIntent方法

package code.xzy.com.handlerdemo;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

public class Main2Activity extends AppCompatActivity {
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        mButton = (Button)findViewById(R.id.btn);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(Main2Activity.this,MainActivity.class));
                finish();
            }
        });
    }
}

列印截圖:


3.RecyclerView相比ListView有哪些優勢

解析:

首先需要解釋下RecyclerView的這個名字了,從它類名上看,RecyclerView代表的意義是,我只管Recycler View,也就是說RecyclerView只管回收與複用View,其他的你可以自己去設定。可以看出其高度的解耦,給予你充分的定製自由(所以你才可以輕鬆的通過這個控制元件實現ListView,GirdView,瀑布流等效果)

其次RecyclerView提供了新增、刪除item的動畫 效果,而且可以自定義

RecyclerView相比ListView優勢在於可以輕鬆實現:

ListView的功能
GridView的功能
橫向ListView的功能
橫向ScrollView的功能
瀑布流效果
便於新增Item增加和移除動畫

不過一個挺鬱悶的地方就是,系統沒有提供ClickListener和LongClickListener。 
不過我們也可以自己去新增,只是會多了些程式碼而已。 
實現的方式比較多,你可以通過mRecyclerView.addOnItemTouchListener去監聽然後去判斷手勢, 

當然你也可以通過adapter中自己去提供回撥

參考

http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1118/2004.html

http://blog.csdn.net/lmj623565791/article/details/45059587

http://www.360doc.com/content/16/0808/14/9200790_581676933.shtml

4.談一談Proguard混淆技術

答案:

Proguard技術有如下功能:

壓縮 --檢查並移除程式碼中無用的類
優化--對位元組碼的優化,移除無用的位元組碼
混淆--混淆定義的名稱,避免反編譯

預監測--在java平臺對處理後的程式碼再次進行檢測

程式碼混淆只在上線時才會用到,debug模式下會關閉,是一種可選的技術。

那麼為什麼要使用程式碼混淆呢?

因為Java是一種跨平臺的解釋性開發語言,而java的原始碼會被編譯成位元組碼檔案,儲存在.class檔案中,由於跨平臺的需要,java的位元組碼中包含了很多原始碼資訊,諸如變數名、方法名等等。並且通過這些名稱來訪問變數和方法,這些變數很多是無意義的,但是又很容易反編譯成java原始碼,為了防止這種現象,我們就需要通過proguard來對java的位元組碼進行混淆,混淆就是對釋出的程式進行重新組織和處理,使得處理後的程式碼與處理前的程式碼有相同的功能,和不同的程式碼展示,即使被反編譯也很難讀懂程式碼的含義,哪些混淆過的程式碼仍能按照之前的邏輯執行得到一樣的結果。

但是,某些java類是不能被混淆的,比如實現了序列化的java類是不能被混淆的,否則反序列化時會出問題。

下面這類程式碼混淆的時候要注意保留,不能混淆。
  Android系統元件,系統元件有固定的方法被系統呼叫。
  被Android Resource 檔案引用到的。名字已經固定,也不能混淆,比如自定義的View 。
  Android Parcelable ,需要使用android 序列化的。
  其他Anroid 官方建議 不混淆的,如
  android.app.backup.BackupAgentHelper
  android.preference.Preference
  com.android.vending.licensing.ILicensingService
  Java序列化方法,系統序列化需要固定的方法。
  列舉 ,系統需要處理列舉的固定方法。
  本地方法,不能修改本地方法名
  annotations 註釋
  資料庫驅動
  有些resource 檔案

  用到反射的地方

5.ANR出現的場景及解決方案

在Android中,應用的響應性被活動管理器(Activity Manager)和視窗管理器(Window Manager)這兩個系統服務所監視。當用戶觸發了輸入事件(如鍵盤輸入,點選按鈕等),如果應用5秒內沒有響應使用者的輸入事件,那麼,Android會認為該應用無響應,便彈出ANR對話方塊。而彈出ANR異常,也主要是為了提升使用者體驗。

解決方案是對於耗時的操作,比如訪問網路、訪問資料庫等操作,需要開闢子執行緒,在子執行緒處理耗時的操作,主執行緒主要實現UI的操作

6.HTTPS中SSL證書認證的過程

7.簡述Android的Activity的內部機制

8.對Android Framework層的某一個模組(或者System App)做簡要介紹

9.Android Handler的機制和原理

主執行緒使用Handler的過程

首先在主執行緒建立一個Handler物件 ,並重寫handleMessage()方法。然後當在子執行緒中需要進行更新UI的操作,我們就建立一個Message物件,並通過handler傳送這條訊息出去。之後這條訊息被加入到MessageQueue佇列中等待被處理,通過Looper物件會一直嘗試從Message Queue中取出待處理的訊息,最後分發會Handler的handler Message()方法中。

這裡寫圖片描述

10.執行緒間通訊和程序間通訊有什麼不同,Android開發過程中是怎麼實現的

11.簡述專案中對於記憶體優化的幾個細節點

答案:

1.當查詢完資料庫之後,及時關閉Cursor物件。
2.記得在Activity的onPause方法中呼叫unregisterReceiver()方法,反註冊廣播
3.避免Content記憶體洩漏,比如在4.0.1之前的版本上不要講Drawer物件置為static。當一個Drawable繫結到了View上,實際上這個View物件就會成為這個Drawable的一個callback成員變數,上面的例子中靜態的sBackground持有TextView物件lable的引用,而lable只有Activity的引用,而Activity會持有其他更多物件的引用。sBackground生命週期要長於Activity。當螢幕旋轉時,Activity無法被銷燬,這樣就產生了記憶體洩露問題。
4.儘量不要在Activity中使用非靜態內部類,因為非靜態內部類會隱式持有外部類例項的引用,當非靜態內部類的引用的宣告週期長於Activity的宣告週期時,會導致Activity無法被GC正常回收掉。
5.謹慎使用執行緒Thread!!這條是很多人會犯的錯誤: Java中的Thread有一個特點就是她們都是直接被GC Root所引用,也就是說Dalvik虛擬機器對所有被啟用狀態的執行緒都是持有強引用,導致GC永遠都無法回收掉這些執行緒物件,除非執行緒被手動停止並置為null或者使用者直接kill程序操作。所以當使用執行緒時,一定要考慮在Activity退出時,及時將執行緒也停止並釋放掉
6.使用Handler時,要麼是放在單獨的類檔案中,要麼就是使用靜態內部類。因為靜態的內部類不會持有外部類的引用,所以不會導致外部類例項的記憶體洩露

12.簡述Android的檢視層級優化,簡述自定義View或者自定義ViewGroup的步驟

個人的理解是,Android檢視渲染必須經過measure、layout、draw三個步驟,measure過程是在一個樹形結構中不斷遍歷的,如果UI層級巢狀很深,必將花費大量的時間,所以應該儘量減少層級巢狀,保證樹的結構扁平,並移除不需要渲染的views

自定義view步驟:

13.選取一個常用的第三方開源庫,簡述其接入步驟

14.TCP和UPD的區別以及使用場景

TCP與UDP基本區別
  1.基於連線與無連線
  2.TCP要求系統資源較多,UDP較少; 
  3.UDP程式結構較簡單 
  4.流模式(TCP)與資料報模式(UDP); 
  5.TCP保證資料正確性,UDP可能丟包 
  6.TCP保證資料順序,UDP不保證 
  
UDP應用場景:
  1.面向資料報方式
  2.網路資料大多為短訊息 
  3.擁有大量Client
  4.對資料安全性無特殊要求
  5.網路負擔非常重,但對響應速度要求高

15.簡述一個設計模式的概念,並簡要談談framework層哪些地方用到了什麼設計模式

單例模式:單例模式是一種物件建立模式,它用於產生一個物件的具體例項,它可以確保系統中一個類只產生一個例項。

介面卡模式:將一個介面轉換成客戶希望的另一個介面,介面卡模式使介面不相容的那些類可以一起工作,其別名為包裝器(Wrapper)

裝飾模式:動態地給一個物件增加一些額外的職責,就增加物件功能來說,裝飾模式比生成子類實現更為靈活。裝飾模式是一種物件結構型模式。

使用場景:
1.在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。
2.當不能採用繼承的方式對系統進行擴充套件或者採用繼承不利於系統擴充套件和維護時可以使用裝飾模式。
優點:
1.對於擴充套件一個物件的功能,裝飾模式比繼承更加靈活,不會導致類的個數急劇增加。
2.可以通過一種動態地方式來擴充套件一個物件的功能。
3.可以對一個物件進行多次裝飾,通過使用不同的具體裝飾類以及這些裝飾類的排列組合。
實際運用:
Android中Context類的實現

外觀模式:主要目的在於讓外部減少與子系統內部多個模組的互動,從而讓外部能夠更簡單得使用子系統。它負責把客戶端的請求轉發給子系統內部的各個模組進行處理。

使用場景:
1.當你要為一個複雜的子系統提供一個簡單的介面時
2.客戶程式與抽象類的實現部分之前存在著很大的依賴性
3.當你需要構建一個層次結構的子系統時

組合模式:將物件以樹形結構組織起來,以達成”部分--整體”的層次結構,使得客戶端對單個物件和組合物件的使用具有一致性。

使用場景:
1.需要表示一個物件整體或部分 層次
2.讓客戶端能夠忽略不同物件層次的變化
優點:
1.高層模組呼叫簡單
2.節點自由增加

模板方法:是通過一個演算法骨架,而將演算法中的步驟延遲到子類,這樣子類就可以複寫這些步驟的實現來實現特定的演算法。它的使用場景:

1.多個子類有公有的方法,並且邏輯基本相同
2.重要、複雜的演算法,可以把核心演算法設計為模板方法

3.重構時,模板方法模式是一個經常使用的模式

觀察者模式:定義物件之間一種一對多依賴關係,使得每當一個物件狀態發生改變時,其相關依賴物件皆得到通知並被自動更新。其使用場景:

1.一個抽象模型有兩個方面,其中一個方面依賴於另一個方面
2.一個物件的改變將導致一個或多個其他物件也 發生改變

3.需要在 系統中建立一個 觸發鏈

具體應用:

比如回撥模式中,實現了抽象類/介面的例項實現了父類提供的抽象方法後,將該方法交還給父類來處理
Listview中的notifyDataSetChanged

RxJava中的觀察者模式

責任鏈模式:是一個請求有多個物件來處理,這些物件是一條鏈,但具體由哪個物件來處理,根據條件判斷來確定,如果不能處理會傳遞給該鏈條中的下一個物件,直到有物件處理它為止。

使用場景:
1.有多個物件可以處理同一個請求,具體哪個物件處理該請求待執行時再確定
2.在不明確指定接收者的情況下,向多個物件中的一個提交一個請求。

實際運用:
Try...catch語句
OrderedBroadcast
MotionEvent:actionDwon actionMove actionUp
事件分發機制三個重要方法:dispatchTouchEvent. onInterceptTouchEvent. onTouchEvent

策略模式:定義一系列的演算法,把它們一個個封裝起來,並且使他們可互相替換。本模式使得演算法可獨立於使用它的客戶而變化。策略模式的使用場景:一個類定義了多種行為,並且這些行為在這個類的方法中以多個條件語句的形式出現,那麼可以使用策略模式避免在類中使用大量的條件語句。

使用場景:
一個類定義了多種行為,並且這些行為在這個類的方法中以多個條件語句的形式出現,那麼可以使用策略模式避免在類中使用大量的條件語句。
優點:
1.上下文Context和具體策略ConcreateStrategy是鬆耦合關係
2.策略模式滿足 開-閉原則
具體應用:
HttpStack

Volley框架

16.位元組流和字元流的區別

位元組流操作的基本單元為位元組;字元流操作的基本單元為Unicode碼元(2個位元組)。
位元組流預設不使用緩衝區;字元流使用緩衝區。

位元組流通常用於處理二進位制資料,實際上它可以處理任意型別的資料,但它不支援直接寫入或讀取Unicode碼元;字元流通常處理文字資料,它支援寫入及讀取Unicode碼元。

17.View的繪製流程,是先測量父View還是先測量子View

18.OOM異常是否可以被try...catch捕獲(或者可否用try-catch捕獲Out Of Memory Error以避免其發生?)

只有在一種情況下,這樣做是可行的:在try語句中聲明瞭很大的物件,導致OOM,並且可以確認OOM是由try語句中的物件宣告導致的,那麼在catch語句中,可以釋放掉這些物件,解決OOM的問題,繼續執行剩餘語句。

但是這通常不是合適的做法。Java中管理記憶體除了顯式地catch OOM之外還有更多有效的方法:比如SoftReference, WeakReference, 硬碟快取等。在JVM用光記憶體之前,會多次觸發GC,這些GC會降低程式執行的效率。如果OOM的原因不是try語句中的物件(比如記憶體洩漏),那麼在catch語句中會繼續丟擲OOM

19.WeakReference和SoftReference的區別

20.請計算一張100畫素*100畫素的圖片所佔用記憶體

21.okHttp實現的原理

22.okHttp有哪些攔截器

23.計算1+2!+3!+4!+5!+...+20!的結果,用程式碼實現

24.寫出單例模式,哪些是執行緒安全的,為什麼是執行緒安全的

25.Retrofit實現原理

26.android圖片有哪些格式

27.sqlite可以執行多執行緒操作嗎?如何保證多執行緒操作資料庫的安全性

答:

每當你需要使用資料庫時,你需要使用DatabaseManager的openDatabase()方法來取得資料庫,這個方法裡面使用了單例模式,保證了資料庫物件的唯一性,也就是每次操作資料庫時所使用的sqlite物件都是一致得到。其次,我們會使用一個引用計數來判斷是否要建立資料庫物件。如果引用計數為1,則需要建立一個數據庫,如果不為1,說明我們已經建立過了。
在closeDatabase()方法中我們同樣通過判斷引用計數的值,如果引用計數降為0,則說明我們需要close資料庫。

 大致的做法就是在多執行緒訪問的情況下需要自己來封裝一個DatabaseManager來管理Sqlite資料庫的讀寫,需要同步的同步,需要非同步的非同步,不要直接操作資料庫,這樣很容易出現因為鎖的問題導致加鎖後的操作失敗。

28.有兩個長度已知的連結串列,怎樣確定兩個連結串列的交集

有以下幾種思路:

(1) 暴力破解 ,遍歷連結串列A的所有節點,並且對於每個節點,都與連結串列B中的所有節點比較,退出條件是在B中找到第一個相等的節點。時間複雜度O(lengthA*lengthB),空間複雜度O(1)。

(2) 雜湊表 。遍歷連結串列A,並且將節點儲存到雜湊表中。接著遍歷連結串列B,對於B中的每個節點,查詢雜湊表,如果在雜湊表中找到了,說明是交集開始的那個節點。時間複雜度O(lengthA+lengthB),空間複雜度O(lengthA)或O(lengthB)。

(3) 雙指標法 ,指標pa、pb分別指向連結串列A和B的首節點。

遍歷連結串列A,記錄其長度lengthA,遍歷連結串列B,記錄其長度lengthB。

因為兩個連結串列的長度可能不相同,比如題目所給的case,lengthA=5,lengthB=6,則作差得到 lengthB- lengthA=1,將指標pb從連結串列B的首節點開始走1步,即指向了第二個節點,pa指向連結串列A首節點,然後它們同時走,每次都走一步,當它們相等時,就是交集的節點。