個人Android面試經驗總結

原文出處: 張濤   php

摘要

「基礎 Android 知識掌握的不錯,學習能力也不錯。可是基礎知識部分比較薄弱,有些概念和邏輯掌握不清。」 感謝春林的這句話。html

MVC,MVP 和 MVVM

  • MVC 通訊方式,環形方式:
    一、View 傳送指令到 Controller
    二、Controller 起到不一樣層面間的組織做用,用於控制應用程序的流程。它處理事件並做出響應。「事件」包括用戶的行爲和數據 Model 上的改變。
    三、Model 將新的數據發送到 View,用戶獲得反饋
    全部通訊都是單向的。
    開源實驗室:圖1

  • MVP 通訊方式:
    一、各部分之間的通訊,都是雙向的。
    二、View 與 Model 不發生聯繫,都經過 Presenter 傳遞。
    三、View 很是薄,不部署任何業務邏輯,稱爲」被動視圖」(Passive View),即沒有任何主動性,而 Presenter很是厚,全部邏輯都部署在那裏。
    開源實驗室:圖2

  • MVVM 模式是 MVP 的升級:
    基本上與 MVP 模式徹底一致。惟一的區別是,它採用雙向綁定:View的變更,自動反映在 ViewModel,反之亦然。
    開源實驗室:圖3
    (以上內容取自:http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html)

咱們針對業務模型,創建的數據結構和相關的類,就能夠理解爲AndroidApp 的 Model,Model 是與 View 無關,而與業務相關的,例如數據庫讀取數據,應該是屬於model層的事情。(感謝@Xander的講解)
個人猜測:

至於爲何咱們一般直接去在 Activity 中去寫數據庫數據讀取,個人猜測是由於簡單。試想,若是是爲了規範,首先定義一個getDataFromDB()的接口,再寫個類實現getDataFromDB()方法,之後若是改了請求數據所用的方法,直接改寫實現類,聽起來確實不錯,但是僅僅是爲了從數據庫讀點數據,額外添加了至少兩個類文件真的有意義嗎。
固然網絡請求,是屬於業務邏輯層C層。

MVP中 Presenter 真正須要處理的並不是業務邏輯,而應該是視圖邏輯。業務邏輯應該是視圖無關的,能夠是單獨的一個類中,也能夠是在P中。
P與V是一對多關係
EventBus應該做用於P層,在P層發送,在P層接收。

MVVM中,M層改變並非直接改變V層,而是經過VM層去改變V層。M與V依舊是不直接操做的。

相關介紹:http://www.tianmaying.com/tutorial/AndroidMVC

架構的定義

有關軟件總體結構與組件的抽象描述,用於指導大型軟件系統各個方面的設計。
總結一下,就是一整個軟件工程項目中的骨架,是一種宏觀的規劃。

Volley相關

Volley的磁盤緩存

在面試的時候,聊到 Volley 請求到網絡的數據緩存。當時說到是 Volley 會將每次經過網絡請求到的數據,採用FileOutputStream,寫入到本地的文件中。
那麼問題來了:這個緩存文件,是聲明在一個SD卡文件夾中的(也能夠是getCacheFile())。若是不停的請求網絡數據,這個緩存文件夾將無限制的增大,最終達到SD卡容量時,會發生沒法寫入的異常(由於存儲空間滿了)。
這個問題的確之前沒有想到,當時也沒說出怎麼回事。回家了趕忙又看了看代碼才知道,原來 Volley 考慮過這個問題(汗!想一想也是)
翻看代碼DiskBasedCache#pruneIfNeeded()

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

private void pruneIfNeeded(int neededSpace) {

    if ((mTotalSize + neededSpace)  mMaxCacheSizeInBytes) {

        return;

    }

 

    long before = mTotalSize;

    int prunedFiles = 0;

    long startTime = SystemClock.elapsedRealtime();

 

    IteratorMap.EntryString, CacheHeader>> iterator = mEntries.entrySet().iterator();

    while (iterator.hasNext()) {

        Map.EntryString, CacheHeader> entry = iterator.next();

        CacheHeader e = entry.getValue();

        boolean deleted = getFileForKey(e.key).delete();

        if (deleted) {

            mTotalSize -= e.size;

        } else {

//print log

        }

        iterator.remove();

        prunedFiles++;

        if ((mTotalSize + neededSpace)  mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {

            break;

        }

    }

}

其中mMaxCacheSizeInBytes是構造方法傳入的一個緩存文件夾的大小,若是不傳默認是5M的大小。
經過這個方法能夠發現,每當被調用時會傳入一個neededSpace,也就是須要申請的磁盤大小(即要新緩存的那個文件所需大小)。首先會判斷若是這個neededSpace申請成功之後是否會超過最大可用容量,若是會超過,則經過遍歷本地已經保存的緩存文件的header(header中包含了緩存文件的緩存有效期、佔用大小等信息)去刪除文件,直到可用容量不大於聲明的緩存文件夾的大小。
其中HYSTERESIS_FACTOR是一個值爲0.9的常量,應該是爲了防止偏差的存在吧(我猜的)。

Volley緩存命中率的優化

若是讓你去設計Volley的緩存功能,你要如何增大它的命中率。
惋惜了,若是上面的緩存功能是昨天看的,今天的面試這個問題就能說出來了。
仍是上面的代碼,在緩存內容可能超過緩存文件夾的大小時,刪除的邏輯是直接遍歷header刪除。這個時候刪除的文件有多是咱們上一次請求時剛剛保存下來的,屁股都還沒坐穩呢,如今當即刪掉,有點捨不得啊。
若是遍歷的時候,判斷一下,首先刪除超過緩存有效期的(過時緩存),其次按照LRU算法,刪除最久未使用的,豈不是更合適?

Volley緩存文件名的計算

這個是我一直沒弄懂的問題。
以下代碼:

Java

1

2

3

4

5

6

private String getFilenameForKey(String key) {

    int firstHalfLength = key.length() / 2;

    String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());

    localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());

    return localFilename;

}

爲何會要把一個key分紅兩部分,分別求hashCode,最後又作拼接。
這個問題以前在stackoverflow上問過 #連接
原諒我,別人的回答我最初並無看懂。直到最近被問到,若是讓你設計一個HashMap,如何避免value被覆蓋,我纔想到緣由。
先來看一下 String#hashCode() 的實現:

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

@Override public int hashCode() {

    int hash = hashCode;

    if (hash == 0) {

        if (count == 0) {

            return 0;

        }

        final int end = count + offset;

        final char[] chars = value;

        for (int i = offset; i  end; ++i) {

            hash = 31*hash + chars[i];

        }

        hashCode = hash;

    }

    return hash;

}

從上面的實現能夠看到,String的hashcode是根據字符數組中每一個位置的字母的int值再加上上次hash值乘以31,這種算法求出來的,至於爲何是31,我也不清楚。
可是能夠確定一點,hashcode並非惟一的。不信你運行下面這兩個輸出:

Java

1

2

System.out.print("======" + "vFrKiaNHfF7t[9::E[XsX?L7xPp3DZSteIZvdRT8CX:w6d;v.hashCode());

System.out.print("======" + "hI4pFxGOfS@suhVUd:mTo_begImJPB@Fl [6WJ?ai=RXfIx^=Aix@9M;;?Vdj_Zsi".hashCode());

這兩個字符串是根據hashcode的算法逆向出來的,他們的hashcode都是12345。逆向算法請見這裏
再回到咱們的問題,爲何會要把一個key分紅兩部分。如今能夠確定的答出,目的是爲了儘量避免hashcode重複形成的文件名重複(求兩次hash兩次都與另外一個url重複的機率總要比一次重複的機率小吧)。
順帶再提一點,就像上面說的,機率小並不表明不存在。可是Java計算hashcode的速度是很快的,應該是在效率和安全性上取捨的結果吧。

推送心跳包是TCP包仍是UDP包或者HTTP包

其實聊起這個問題是由於最近看到 @睡不着起不來的萬宵 同窗寫的一篇文章《Android推送技術研究》結果就產生了這個沒回答出來的問題(媽蛋,本身給本身挖坑 – -)
最後看了這篇文章(好像是轉的,沒找到原地址)android 心跳的分析
原來心跳包的實現是調用了socket.sendUrgentData(0xFF)這句代碼實現的,因此,固然是TCP包。

ARGB_8888佔用內存大小

首先說說本題的答案,是4byte,即ARGB各佔用8個比特來描述。當時回答錯了,詳細解答看這裏你的 Bitmap 究竟佔多大內存
可是——
這個問題引出了一個大大的鬧劇,請聽我慢慢道來。

不知道怎麼就聊到 Bitmap 壓縮上了,他說他們的Bitmap竟然都是不壓縮的

仍是直接甩代碼吧。。。。

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

public static Bitmap create(byte[] bytes, int maxWidth, int maxHeight) {

//上面的省略了

        option.inJustDecodeBounds = true;

        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, option);

        int actualWidth = option.outWidth;

        int actualHeight = option.outHeight;

 

        // 計算出圖片應該顯示的寬高

        int desiredWidth = getResizedDimension(maxWidth, maxHeight, actualWidth, actualHeight);

        int desiredHeight = getResizedDimension(maxHeight, maxWidth, actualHeight, actualWidth);

 

        option.inJustDecodeBounds = false;

        option.inSampleSize = findBestSampleSize(actualWidth, actualHeight,

                desiredWidth, desiredHeight);

        Bitmap tempBitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, option);

 

        // 作縮放

        if (tempBitmap != null

                & (tempBitmap.getWidth() > desiredWidth || tempBitmap

                .getHeight() > desiredHeight)) {

            bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth,

                    desiredHeight, true);

            tempBitmap.recycle();

        } else {

            bitmap = tempBitmap;

        }

    }

 

    return bitmap;

}

你這麼作,decodeByteArray兩次不是更佔內存嗎?
第一次設置inJustDecodeBounds = true 時候是不佔內存的,由於返回的是null
一臉不相信個人說:噢,這地方我下去再看看。
嚇得我回來了之後趕忙又看了看,還好沒有記錯,見源碼註釋

Java

1

2

3

4

5

6

/**

* If set to true, the decoder will return null (no bitmap), but

* the out... fields will still be set, allowing the caller to query

* the bitmap without having to allocate the memory for its pixels.

*/

public boolean inJustDecodeBounds;

Activity中相似onCreate、onStart運用了哪一種設計模式,優勢是什麼

這個回答的太多了,我當時說的是代理模式,由於AppCompatActivity中的確是使用的代理模式。這一點還要感謝@代碼家 當時說讓我看看AppCompatDelegate類的設計。其主要目的就是經過使用組合來替代繼承,下降了耦合。
不過回家後再想想,對方想聽到的應該是模板方法模式吧。在父類中實現一個算法不變的部分,並將可變的行爲留給子類來實現。生命週期方法本來就是在基類中作出了Activity不一樣狀態時回調的一系列方法,而這些方法具體須要作的可變部分交給子類去完成。

HashMap的底層實現

HashMap內部是經過數組實現的,誒,大學時候數據結構有講過啊,都忘記了。根據hash算法,求出當前key應該存放在數組的那個index處,若是有值了,則存在相鄰的下一個位置。
根據若是本身實現HashMap如何防止value覆蓋。同上面 Volley 中講到的。

Atomic、volatile、synchronized區別

面Java基礎的時候趕上了這個問題,說若是隻有一個i++;的時候,volatilesynchronized可否互換。當時也不知道,感受volatile做爲修飾變量的時候,變量自加會出現加到一半發生線程調度。再看看當時蒙對了。
volatile 能夠保證在一個線程的工做內存中修改了該變量的值,該變量的值當即能回顯到主內存中,從而保證全部的線程看到這個變量的值是一致的。可是有個前提,由於它不具備操做的原子性,也就是它不適合在對該變量的寫操做依賴於變量自己本身。就好比i++、i+=1;這種。可是能夠改成num=i+1;若是i是一個 volatile 類型,那麼num就是安全的,總之就是不能做用於自身。
synchronized是基於代碼塊的,只要包含在synchronized塊中,就是線程安全的。
既然都說了線程安全,就多瞭解幾個:
AtomicInteger,一個輕量級的synchronized。使用的並非同步代碼塊,而是Lock-Free算法(我也不懂,看代碼就是一個死循環調用了底層的比較方法直到相同後才退出循環)。最終的結果就是在高併發的時候,或者說競爭激烈的時候效率比synchronized高一些。
ThreadLocal,線程中私有數據。主要用於線程改變內部的數據時不影響其餘線程,使用時須要注意static
詳細分析見這篇文章 。
再補一個,才學到的。利用clone()方法,若是是一個類的多個對象想共用對象內部的一個變量,而又不想這個變量static,可使用淺複製方式。(查看設計模式原型模式)

其餘

作內部庫設計時,最重要的考慮是jar的成本,方法數、體積。
設計模式不該該是去記憶,而應該是用的時候天然而然的用上。

3月11日更新
面試真的是有夠煩的,由於題目是隨機的,而知識是無窮的。直到被不少答案都是沒有標準的。就好像上面提到的 MV* ,也許到如今上面的理解依舊有問題,可是我以爲架構是死的,而最合適的纔是最好的。
可是有一點,面試也是一種學習,至少它能讓你知道你的薄弱點在哪。

問啊APP,程序員答題神器,解決你全部的技術難題,   (上問啊APP 瞭解更多) http://t.cn/R4vE2d7 下載註冊送5元 快去下載註冊吧! 



問啊-定製化IT教育平臺,牛人一對一服務,有問必答,開發編程社交頭條 官方網站:www.wenaaa.com 下載問啊APP,參與官方懸賞,賺百元現金。


QQ羣290551701 彙集不少互聯網精英,技術總監,架構師,項目經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!

相關文章
相關標籤/搜索