Adnroid面試基礎

1:android 啓動模式

1.1:standard 模式
默認的啓動模式,每次創建都會生成新的activity實例。沒有特殊需求直接使用默認的就行。
1.2 singleTop 模式
根據棧頂是否有該activity實例,如果有則直接重用該實例,這時,intent的參數應該從onNewIntent()方法中獲取。
否則,則生成新的實例。
該模式可適用與閱讀類或者商品類詳情頁,亦或者推送的跳轉頁面。
1.3singleTask 模式
根據棧中是否有實例,如果有則重用該實例,並將該activity上面的實例清出棧,使其位於棧頂,否則就重新生成實例。
主頁面可使用該mode。
1.4singleInstance 模式
生成一個新的棧,且這個棧中只有這一個activity實例。

2:單例模式

2.1:懶漢式

public class SingleTon {
   private static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

從上數代碼我們可以看到,實例化被延遲加載,從而節約資源,但是當處於多線程環境時,如果多個線程同時進入uniqueInstance == null時,就會多次實例化對象。所以,懶漢式是線程不安全的。

2.2:線程安全的懶漢式
那麼如何保證同一時間,只有一個一個線程進入該方法呢,答案是:加鎖。

public static synchronized Singleton getUniqueInstance() {
    if (uniqueInstance == null) {
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

但是這時候會有一個問題,當一個線程進入後,別的想要進入這個方法的線程都需要等待,即使是uniqueInstance 已經實例化。所以這樣會造成阻塞時間過長,從而造成性能問題。

2.3:雙重校驗鎖

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

首先判斷uniqueInstance 是否初始化,只有初始化了才需要加鎖判斷。
如果兩個線程同時滿足uniqueInstance == null ,進入if語句,雖然語句添加了同步鎖,但是隻是滿足了線程的先後,還是會初始化兩次。
所以,在同步語句中依然需要判斷 uniqueInstance ==null。
也就是說
第一個判斷條件避免初始化後的加鎖操作,
第二個則判斷條件則避免出現多次初始化的問題。
接着我們看下uniqueInstance = new Singleton()。
初始化對象是非原子操作,可以抽象成三步jvm指令來看:

1:分配對象的內存空間 
memory =allocate(); 
2:初始化對象 
ctorInstance(memory); 
 3:設置uniqueInstance 指向剛分配的內存地址
uniqueInstance =memory; 

其中,2初始化對象依賴於1,但是3指向內存地址則不依賴於2.
所以,jvm是可以對其進行指令重排的。
改完後順序變成1-3-2、
也就是將uniqueInstance 指向分配內存地址放到了初始化的前面,這時,如果線程1在初始化之前,就將其分配給uniqueInstance 引用,而正好線程2進入方法判斷uniqueInstance 引用不爲null,直接返回就會出錯。
所以對於這種情況,我們需要使用volatile關鍵字修飾uniqueInstance 。

 private volatile static Singleton uniqueInstance;

volatile關鍵字可以禁止指令重排。

2.4:餓漢式

餓漢式單例是直接對對象進行初始化,所以也就避免了多次初始化的線程不安全問題,但是卻不能做到延遲實例化節約資源。


public class SingleTon {
    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        return uniqueInstance;
    }
}

3:雙緩衝機制

問題由來:
cpu訪問內存的速度要遠遠高於訪問屏幕的速度。如果要繪製多個複雜的佈局,每次都訪問內存獲取圖形,在一次次繪製到屏幕從而多次訪問屏幕,會十分耗時。
第一層緩衝:
先在內存中將所有圖形繪製到bitmap中,然後再將其一次性繪製到屏幕。
android 中onDraw()就實現了這種緩衝,因爲它不是繪製一下就顯示下,而是繪製完成後全部顯示出來。
第二層緩衝:
因爲onDraw是在ui線程,所以如果這裏進行大量操作,是十分耗時的,所以第二層緩衝,則是創建另一個緩存對象存儲第一層緩衝的bitmap,然後將這個緩存對象在傳到ondraw中。
類似surfaceView的實現,則是使用創建子線程,然後通過lockCanvas獲取canvas來繪製,然後通過
SurfaceHolder的unlockCanvasAndPost方法釋放canvas並提交更改。

4:handler內存泄漏

首先我們先看下常用的handler的一段代碼:

Handler handler=new Handler(){
    @Override
    public void handleMessage(Message msg) {
        //更新UI
        super.handleMessage(msg);
    }
};

Thread thread=new Thread(new Runnable() {
    @Override
    public void run() {
        //耗時操作
        handler.sendEmptyMessage(0);
    }
}).start();

如上述代碼,如果當網絡請求或者耗時操作正在進行,用戶就直接退出activity,那麼這時,雖然界面銷燬,但是後臺任務仍然進行,由於線程中引用當前activity對象handler,所以activity並不會被回收,這時就有兩個問題,第一:當後臺任務執行完畢,更新ui操作時,會報空指針。第二:由於activity不能被回收,一直佔用資源。當用戶來回切換activity時,資源佔用越來越大,最終必會導致oom。

那麼如何解決呢?
首先:當activity銷燬時,我們可以直接停止後臺線程任務。
其次:應該使用handler的removeCallbacks方法,把消息對象從消息隊列移除。
另外,我們可以使用靜態內部類,這樣就不持有外部類的對象,activity就可以回收了,

private MyHandler mHandler=new TestHandler();
static class MyHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        //更新界面操作
        super.handleMessage(msg);
    }
}

如上代碼,雖然解決了activity回收的問題,但是我們無法更新ui了。

這時,我們可以使用弱引用,傳入當前的activity。

 private WeakReference<xxx> mContext;
    public MyHandler(xxx activity){
        mContext=new WeakReference<>(activity);
    }

5:android進程

前臺進程:
正在與用戶交互的activity或者activity用到的進程,此進程在內存不足的情況下最後被殺死。
可見進程:
處於onPaused狀態的activity或者綁定在上面的service,也就是說被用戶可見但是失去焦點,無法交互。
服務進程:
正在運行但不可見,例如:正在下載但不在下載界面。
後臺進程
運行執行者onstop的service,用戶不在關心,如退出app。
空進程
不包含任何應用組件的進程,系統需要內存時,最先殺死。

android app默認只有一個進程,進程名稱就是包名。

6:數組和鏈表區別

1:數組內部是連續的一塊內存;鏈表在內存中可以存在任何地方,不要求連續,是通過指針指到下一個元素的。
2:數組需要預留空間,所以有可能會浪費內存空間。
3:數組插入刪除慢,查詢快,可以通過下標迅速訪問元素;鏈表插入刪除快,查詢慢,不能隨機查找。
4:數組指定大小,不利於擴展。鏈表大小不用定義,數據隨意增刪。

7:類中方法順序

靜態代碼塊—>非靜態代碼塊—>構造方法

public class MyTest {

    public static void main(String[] args) {
        Father father = new Child();
        father = new Child();
    }

}

class Father{
    public  Father(){
        System.out.println("父類:構造");
    }
    static {
        System.out.println("父類:靜態初始化塊");
    }

}

class Child extends Father{
    public Child(){
        System.out.println("子類:構造");
    }
    static {
        System.out.println("子類:靜態初始化塊");
    }

執行順序:
在這裏插入圖片描述

8:TCP三次握手和四次揮手

三次握手:

第一次:client將標誌位SYN(同步序號)設置爲1,同時產生一個隨機序號seq =k,
然後將數據包發給server
第二次:server將SYN=1,同時也生成一個隨機seq=j,ACK=1(確認序號標誌),ack=k+1,發送給client。
第三次:client將ACK=1,ack=j+1,發送給server。

當第一次握手後,如果時間過長,客戶端不需要這次連接了,服務端就建立了連接,無疑是浪費資源的。
四次揮手:

第一次:client發送請求,FIN=1,seq=k;
第二次:server接收到seq=k後,發回client的是ACK=1,ack=k+1,同時生成seq=j。這時server如果有數據繼續傳送。
第三次:當沒有數據傳輸時,server繼續發送ACK=1,ack=k+1,seq=w,FIN=1.
第四次:client收到seq=w,發送新的到server:ack=w+1,seq=k+1,ACK=1.

9:jvm內存管理

1:方法區:
方法區存放了類的相關信息,如類名,修飾符,常量,方法等,另外還有靜態變量,構造函數,final修飾的常量等。方法區是全局共享的,當方法區超過允許的大小時,就會拋出OOM.

2:堆
堆區由所有線程共享,當虛擬機啓動時創建。
這裏主要存放對象實例和數組,所有new出來的對象,都存放在堆區。

3:虛擬機棧

虛擬機棧佔用的是系統內存,每個線程私有。虛擬機相關的異常有棧溢出OOM.當線程調度的棧深度大於虛擬機限定的值時,就會拋出棧溢出。不過大多數虛擬機可以動態擴展,當擴展到一定程度,超出內存時,就會引發OOM.

4:本地方法棧

本地方法棧用於支持native方法,記錄了native方法的執行狀態。

5:程序計數器
程序計數器是一塊很小的內存區域,位於cpu上,每個程序計數器只記錄一個線程的行號,所以它是線程私有的。字節碼解釋器工作時,就是通過修改程序計數器的值來取得下一條指令的。

10:GC相關

當垃圾回收執行的時候,其餘線程都會被停止,所以儘可能讓垃圾回收執行的時間短。
運行的條件:
1:GC在優先級最低的線程中執行,當沒有應用運行時被調用。
2:java堆內存不足時,jvm會強制調用gc線程,

判斷對象是否存活:引用計數算法和可達性分析算法(根搜索算法)
經典算法:引用計數算法將對象添加進計數器,當對象被引用,計數器+1,失去引用則-1,一段時間內該對象的計數爲0時,則該對象可以被回收。
缺點:當兩個對象相互引用,則無法回收。

Object obj1=new Object();//obj1引用計數=1;
Object obj2=obj1;//obj1引用計數+1;
Object obj3=new Object();

obj2=null;//obj1引用計數-1;
obj1=obj3;//obj1引用計數=0;可以回收。


根搜索算法:從GC roots(根節點)向下搜索,如果有一個對象無法到達gc roots,則說明這個對象可以回收。

java中作爲gc root的對象包括:
1:虛擬機棧中引用的對象
2: 方法區中靜態屬性引用的對象
3:方法區中常量引用的對象
4:本地方法棧中native引用的對象。

gc分類:
新生代:新生代的目標就是儘可能的快速的收集掉生命週期短的對象。一般情況下,新創建的對象都會存入新生代。
新生代的內存按照8:1:1的比例分爲一個eden區(伊甸園
)和兩個survior(倖存者)區。
大部分的對象在eden中生成,當觸發mimor gc(新生代的gc),先使用複製算法,將eden區的對象複製到survior0中,然後清空eden區,如果survior0也滿了時。就將eden區和survior0中存活的對象轉入survior1中,然後交換survior0和survior1(下次觸發gc時,掃描eden和survior1),此時survior0位空,如此循環、
當survior1不足以存放eden和survior0中存活的對象後,則將存活對象直接存入老年代。
新生代的minor gc比較頻繁。full gc頻率較低、

老年代:存放生命週期比較長的對象,如在minor gc觸發了多次都沒有清理掉的對象,老年代的內存也相對年輕代較大,差不多2:1。當老年代的內存滿時就會觸發full gc。
持久代:用於存放靜態文件,如java類,方法。

類加載器:
啓動類加載器:負責將java_home\lib下,並且虛擬機識別的類庫加載到虛擬機內存中。
擴展類加載器:負責將java_home\llib\ext的所有類庫加載到虛擬機內存,擴展類記載器開發者可以直接使用。
應用程序加載器:由App-ClassLoader實現,這個類加載器可以通過getSystemClassLoader()獲取,所以也可以叫做系統類加載器。
它負責將classPath下的類庫加載。

類加載機制:
雙親委派:當一個加載器收到了類加載的請求,首先不會自己嘗試加載這個類,而是拋給父類加載器,每一個層次的類加載器都是如此,所以最後都是傳送到頂層的啓動類加載器中,只有父類無法加載或者加載失敗,纔會自己嘗試加載。

11:序列化

序列化:就是將對象轉成二進制流,便於存儲和傳輸。
serializable:可能會頻繁的觸發io操作,存儲在磁盤上,效率較慢。
Parcelable:android 給出的一套序列化方法,將對象轉成二進制流存入共享內存,別的對象讀取流,反序列化成對象即可;效率高。

12:android 的內存緩存和磁盤緩存

內存緩存基於Lrucache實現;
磁盤緩存基於DiskLrucache實現;
這兩個都又基於lru算法和linkHashMap實現。

lru算法:最近最久未使用。也就是說,一條數據在最近的一段時間沒有被使用,則他在將來的一段時間被訪問的可能性也很小,則優先清除這類數據。

linkHashMap:繼承自hashmap,數組+鏈表的結構。
所有節點組成雙向鏈表;
有一個accessOrder的boolean值,如果是false,則默認是FIFO算法,如果是true,則是lru訪問。

13:java線程

線程的調度:協同式線程調度和搶佔式線程調度。
協同式線程調度:線程的執行時間由線程控制,線程結束後,會主動通知系統切換線程。所以,實現簡單,不存在線程同步,不過這樣也存在一個線程導致整個進程阻塞的問題。
搶佔式線程調度:java使用的就是這種方式,每個線程由系統來分配執行時間,線程的切換也不由線程本身來決定。

線程狀態:

創建(new):創建後尚未啓動的線程
運行(runable):這裏包括操作系統線程狀態的Running和ready兩個狀態,也就是說,處於此狀態的線程可能正在執行,也可能等待cpu分配執行時間。
無限期等待(waiting):處於此狀態的線程,cpu不會給他分配執行時間,需要等待其他線程喚醒。調用沒有設置timeout的Object.wait()或者Thread.join()可以進入無限期等待狀態。
限期等待(Timed waiting):cpu同樣不會分配執行時間,不過此狀態不需要其他線程顯式喚醒。在一定時間後由系統自動喚醒。
Thread.sleep;設置了timeout的Object.wait()和Thread.join()方法;可以使線程進入限期等待狀態。
阻塞(blocked):線程被阻塞,阻塞狀態與等待狀態的區別是:阻塞狀態在等待獲取到一個排它鎖,這個事件在另一個線程放棄這個鎖的時間發生。
在程序等待進入同步狀態的時候,線程將進入這個狀態。
結束(Terminated):線程結束執行。

14:hashmap相關

hashmap底層數組+鏈表;當鏈表長度大於8時,鏈表會轉爲紅黑樹.

hash碰撞:當key計算得到的hashcode相同時,就會產生碰撞。
hashmap進行put操作時,首先計算key在bucket(hash桶)中的index(如取模算法),如果index相同就會產生碰撞,以鏈表形式存在bucket中,如果沒有碰撞並且不存在這個節點,就直接插入,否則就替換。如果bucket超過閥值,負載因子0.75,默認長度16.也就是12時會擴容至2倍.

hashmap進行get操作時,首先通過key進行hash運算,獲取bucket位置,如果發生衝突在通過equals方法獲取真正的value。
使用Map m = Collections.synchronizeMap(hashMap);實現hashmap的同步、

15:activity,window和view的關係

activity->phonewindow->decorView->titlebar+contentview.
activity通過setcontentview來將phonewindow和activity關聯起來。
phonewindow內部包含一個viewgroup,setcontentview就是將layout設置到這個viewgroup上面,而View通過WindowManager的addView()、removeView()、updateViewLayout()對View進行管理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章