歡迎光臨
每天分享高質量文章

Android 實習生面試經歷記錄

作者:念人遠鄉

連結:https://www.jianshu.com/p/3cd5ef51eed5

從 2018.12.28 的第一次面試到 2019.01.09 整整橫跨了一年。也面試了幾家公司的 Android 實習僧的崗位。

有大廠:滴滴、豬廠、位元組等
也有中廠:玩吧App等
也有小廠:xxxxxx

給我的感覺就是

大廠更註重你的基本知識,你對待一個問題的思路。是否是以一個工程師的角度去看待問題。也更註重資料結構&&演演算法(我的痛)這一塊的知識的考察。不會是簡單的考你排序、查詢這種考研層面上的東西。小廠的話,大概是因為需要即插即用,也沒有很好的導師類資源,更看重你的自我學習能力和一個自我學習的過程,以及實際開發的能力。好了,話不多說,我們就直接進入面試的總結。題目旁邊會標註是大廠中廠還是小廠的面試題,就不按公司整理啦,如果是All就是基本都會考到(需要重點理解)(部分問題的答案是自己 google 後的結果和學習的結果,部分問題的答案也來自釐米姑娘小姐姐的簡書裡的三份安卓面試題解(真的很詳細很感謝了!))

一、四大元件相關問題

    • 是否瞭解Activity的四種啟動樣式?(All)

 

四種啟動樣式:Standerd、SingleTop、SingleTask、SingleInstance。

  • Standard(預設標準啟動樣式):每次啟動都重新建立一個新的實體,不管它是否存在。且誰啟動了這個Acitivity,那麼這個Acitivity就執行在啟動它的那個Acitivity的任務棧中。

  • SingleTop(棧頂復用樣式):如果新的Activity已經位於任務棧的棧頂,那麼不會被重新建立,而是回呼onNewIntent()方法,透過此方法的引數可以取出當前請求的資訊。

  • SingleTask(棧內復用樣式):這是一種單例樣式,在這種樣式下,只要Acitivity在一個棧中存在,那麼多次啟動此Acitivity都不會重建實體,而是回呼onNewIntent方法。同時由於SingleTask樣式有ClearTop功能,因此會導致所要求的Acitivity上方的Acitivity全部銷毀。

  • SingleInstance(單實體樣式):和棧內復用類似,此種樣式的Acitivity只能單獨位於一個任務棧中。全域性唯一性。單例實體,不是建立,而是重用。獨佔性,一個Acitivity單獨執行在一個工作棧中。

  • 如果假設A是Standard,B是SingleTop,C是SingleTask,D是SingleInstance的啟動樣式,那麼以A->B->C->D->A->B->C->D這種情況開啟Activity,分析一下最後的工作棧是怎樣的情況?(大廠)

此題在理解啟動樣式的情況下解答。

棧內情況
  • Activity的生命週期?在橫豎屏轉換時候的生命週期流程是怎樣的?(小廠)

  • onCreate()表示 Activity 正在建立,常做初始化工作,如 setContentView介面資源、初始化資料。
    onStart()表示Activity 正在啟動,這時Activity 可見但不在前臺,無法和使用者互動。
    onResume()表示Activity 獲得焦點,此時Activity 可見且在前臺並開始活動。
    onPause()表示Activity 正在停止,可做 資料儲存、停止動畫等操作。
    onStop()表示activity 即將停止,可做稍微重量級回收工作,如取消網路連線、登出廣播接收器等。
    onDestroy()表示Activity 即將銷毀,常做回收工作、資源釋放。
    onRestart()表示Activity由不可見到可見的過程,Activity重新啟動。

  • 螢幕旋轉時的生命流程:
    onPause()
    onSaveInstanceState()
    onStop()
    onDestroy()
    onCreate()
    onStart()
    onRestoreInstanceState()

  • onResume()為了避免由於配置改變導致 Activity 重建,可在AndroidManifest.xml中對應的 Activity中設定android:configChanges=”orientation|screenSize”。此時再次旋轉螢幕時,該Activity不會被系統殺死和重建,只會呼叫onConfigurationChanged。

  • 在 SingleTop 樣式中,我如果開啟一個已經存在棧頂的Activity,他的生命流程是怎樣的?(小廠)

開啟第一個A:A.OnCreate()->A.onStart()->A.onResume()
此時由A跳轉至B:A.onPause()->B.onCreate()->B.onStart()->B.onResume()->A.onStop()
此時B的啟動樣式是棧頂樣式,再由B開啟B:B.onPause()->B.onNewIntent()->B.onResume()

  • Service簡要介紹一下?(基本都會問,但不可能只說概念上的,需要展開All)

Service是Android中實現程式後臺執行的一種解決方案,適合執行那些不需要與使用者互動而且要求長期執行的任務。

  • Service的兩種啟動方式簡要介紹一下吧?(大廠、小廠)

①元件透過呼叫 Context 的 StartService()方法啟動一個服務,回呼服務中的onStartCommand()。如果該服務還沒有被建立,則回呼的順序為 onCreate()->onStartCommand()。服務被啟動後會一直儲存執行的狀態,直到 StopService()或者 StopSelf() 方法被呼叫,服務停止並回呼 onDestroy()。無論呼叫多少次StartService()只需要呼叫一次 StopService() 就能終止服務。
②元件透過呼叫 Context 的 bindService() 可以系結一個服務,回呼服務中的onBind() 方法。類似地,如果該服務之前還沒建立,那麼回呼的順序是onCreate()->onBind()。之後呼叫方可以獲取到 onBind() 方法裡傳回的IBinder 物件的實體,從而實現和服務的通訊。直到呼叫了 unBindService() 方法使服務終止,回呼順序 onUnBind()->onDestroy()。

  • Service 如何和 Activity 進行通訊?(中廠)

①透過系結服務的方式。在系結的服務中宣告一個Binder類,並建立一個Binder物件,在onBind()函式中傳回這個物件,並讓Activity實現ServiceConnection介面,在OnServiceConnected方法中獲取到Service提供的這個Binder物件,透過這個物件的各種自定義的方法就能完成Service與Activity的通訊。
②透過Intent的方式,在StartService()中需要傳入一個Intent物件作為引數,透過這個Intent實體物件進行實現通訊。
③透過Callback和Handler的方式,在系結的服務中宣告一個Binder類,並建立一個Binder物件,在onBind()函式中傳回這個物件,讓Activity實現ServiceConnection介面,並且在OnserviceConnected方法中實體化Service中的CallBack介面,並且實現OnDataChange()方法,其中的實質是一段Handler程式碼,可以在其中完成耗時操作,以這種方式完成通訊。

  • Android 資料持久化的方式有瞭解過嘛?ContentProvider 有接觸過嗎?(小廠)

①File檔案儲存方式:寫入讀取檔案和Java中實現IO類似
②SharePreferences儲存:一種輕型的資料儲存方式,適用於基本型別資料,本質是以鍵值對
存在的XML檔案。
③SQLite資料庫儲存:   一款輕量級的關係型資料,運算速度快,資源佔用少,儲存複雜的關係型資料時候使用
④ContentProvider:四大元件,用於資料的儲存和共享,不止侷限於資料被該應用程式使用,且能讓不同的應用之間進行資料共享,還能透過對指定的一部分資料進行共享,從而保證隱私資料不會有洩露的風險。

  • ContentProvider是安卓的四大元件之一,主要負責資料的儲存和共享。與檔案儲存、sharePreferences儲存、SQLite儲存方式不同的是,後者儲存的資料只能被該應用程式使用,而前者所儲存的資料可以讓不同應用之間的資料進行共享,還可以對指定的一部分資料進行共享,從而保證隱私資料不會有洩露的風險。

  • 聊聊 Android 的廣播機制吧?(All)

  • 廣播是一種運用在應用程式之間傳輸資訊的機制,Android中我們傳送廣播內容實質是一個Intent,這個Intent中可以攜帶我們要傳送的資料。(當然也不可以像Service一樣簡單的談談概念性的東西,你可以深入的展開。)

  • 譬如廣播的三大種類
    ①普通廣播:一種完全非同步執行的廣播,在廣播發出之後,所有的廣播接收器幾乎都會在同一時刻接收到這條廣播訊息,因此它們接收的先後是隨機的。
    ②有序廣播:一種同步執行的廣播,在廣播發出之後,同一時刻只會有一個廣播接收器能夠收到這條廣播訊息,當這個廣播接收器中的邏輯執行完畢後,廣播才會繼續傳遞,所以此時的廣播接收器是有先後順序的,且優先順序(priority)高的廣播接收器會先收到廣播訊息。有序廣播可以被接收器截斷使得後面的接收器無法收到它。
    ③本地廣播:發出的廣播只能夠在應用程式的內部進行傳遞,並且廣播接收器也只能接收本應用程式發出的廣播。

  • 或者廣播的兩大註冊方式?

  • 靜態註冊:
    ① 建立一個廣播接受器類,在onReceive()方法中Toast一段資訊。
    ② 在AndroidMainfest.xml中註冊才可以使用。

  • 動態註冊:
    新建一個類,繼承自BroadCastReceiver,並重寫OnReceive函式。
    由於動態註冊是實現在OnCreate方法中的,因此存在一個缺點,必須啟動後才能接受到廣播。未啟動之下就能接收到廣播的話,使用靜態註冊廣播接收器。

  • 或者再深入可以聊聊本地廣播的原始碼角度分析其高效、安全、內部協作是如何實現的。這裡我們就不展開了。

二、Android 訊息機制

  • 簡單的描述一下Handler訊息傳遞機制是怎麼實現的?(All)

① 概述:Handler是可以透過傳送和處理Message和Runnable物件來進行訊息傳遞,是一種非同步訊息機制。可以讓對應的Message和Runnable在未來的某個時間點進行相應的處理。讓耗時操作在子執行緒裡執行,讓更新UI的操作在主執行緒中完成,而子執行緒和主執行緒之間的通訊就是靠Handler實現的。
② 成員:

  • Message(訊息):是執行緒之間傳遞的資訊,可以攜帶少量的資訊,用於在不同執行緒之間交換資料。

  • Handler(處理者):負責Message的傳送及處理。透過 Handler.sendMessage() 向訊息池傳送各種訊息事件;透過 Handler.handleMessage() 處理相應的訊息事件。

  • MessageQueue(訊息佇列):用來存放Handler傳送過來的訊息,內部透過單連結串列的資料結構來維護訊息串列,等待Looper的抽取。

  • Looper(訊息泵):透過Looper.loop()不斷地從MessageQueue中抽取Message,按分發機制將訊息分發給標的處理者。
    ③ 流程:

    Handler資訊傳遞機制
  • Handler.sendMessage()傳送訊息時,會透過MessageQueue.enqueueMessage()向 MessageQueue 中新增一條訊息;

  • 透過 Looper.loop() 開啟迴圈後,不斷輪詢呼叫 MessageQueue.next();

  • 呼叫標的 Handler.dispatchMessage() 去傳遞訊息,標的 Handler 收到訊息後呼叫 Handler.handlerMessage() 處理訊息。

  • 子執行緒中使用 Handler 需要註意什麼?(中廠)

和在主執行緒中直接 new 一個 Handler 不同,由於子執行緒的 Looper 需要手動去建立,需要手動編寫 Looper.loop() 與 Looper.prepare() 方法。

            @Override
            public void run() {
                Looper.prepare();//呼叫Looper.prepare()
                new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                }
            };
                Looper.loop(); //呼叫Looper.loop()
            }
        }).start();

 

  • Hanlder 的 postDealy()呼叫後訊息佇列會發生什麼變化?(中廠)

這題我當時沒回答上來,回來以後仔細查看了相關的答案。在這裡直接取用的釐米姑娘小姐姐的答案回答的這個問題。

  • post delay 的 Message 並不是先等待一定時間再放入到 MessageQueue 中,而是直接進入並阻塞當前執行緒,然後將其 delay 的時間和隊頭的進行比較,按照觸發時間進行排序,如果觸發時間更近則放入隊頭,保證隊頭的時間最小、隊尾的時間最大。此時,如果隊頭的 Message 正是被 delay 的,則將當前執行緒堵塞一段時間,直到等待足夠時間再喚醒執行該 Message,否則喚醒後直接執行。

  • 簡要解釋一下 ANR?為什麼會發生 ANR?如何避免發生 ANR?如何定位 ANR?那你還瞭解哪些執行緒間切換的類?簡要選一個進行一下闡述吧?(大廠)

先說點無關的,這是一連串的提問,涉及到效能最佳化->ANR問題定位->訊息傳遞->實現原理,所以在面試的時候你儘量不要選擇他問一個你答一句話,你可以把你有把握,之前有所準備的內容,按照一個條理跟面試官闡述清楚,他大概會好感度UP。當然必須要對你所說的東西保證充分的熟悉,因為他基本都會抓著你所說的繼續跟你深入探討下去。當然實習僧不會問的特別深入,但是你如果能把原理、機制都跟他說的很清晰有條理且有結構的話。會讓面試官對你的好感有一定的上升(我猜的但應該沒猜錯)。也就是我們開題的時候所說的用工程師的眼光看待問題。什麼問題?為什麼發生問題?怎麼定位並解決問題?

  • 以下是我對這道題的回答,只是參考,如果有錯誤希望指正:
    ① ANR(Application Not Responding,應用程式無響應):當操作在一段時間內系統無法處理時,譬如:應用在5秒內未相應使用者的輸入事件。廣播接收器10秒內未完成相關的處理。服務20秒內無法處理完成,那麼會在系統層面會彈出應用程式無響應的對話方塊。
    ② 所以為了避免發生ANR,我們儘量使用多執行緒,不要在主執行緒做耗時操作,而是透過開子執行緒,把耗時的工作放在工作執行緒中處理。所使用的方法比如繼承自Thread類、實現Runnable介面、使用AsyncTask、IntentService、HandlerThread等機制。
    ③ 如果發生了ANR則可以透過data/anr找到traces.txt檔案確定ANR發生的原因。
    ④ 關於上面說的這些多執行緒機制,如果您感興趣的話我可以簡要的跟你闡述其中的一個或幾個機制的具體內容。比如AsyncTask機制,AsyncTask機制底層封裝了執行緒池和Handler,便於執行後臺任務以及在子執行緒中進行UI操作。使用AsyncTask要理解3個泛型引數和4個方法。

  • Params: 這個泛型指定的是我們傳遞給非同步任務執行時的引數的型別。

  • Progress: 這個泛型指定的是我們的非同步任務在執行的時候將執行的進度傳回給UI執行緒的引數的型別。

  • Result: 這個泛型指定的非同步任務執行完後傳回給UI執行緒的結果的型別。
    我們在定義一個類繼承AsyncTask類的時候,必須要指定好這三個泛型的型別,如果都不指定的話,則都將其寫成Void。
    4個方法:當我們執行一個非同步任務的時候,其需要按照下麵的4個步驟分別執行
    onPreExecute(): 這個方法是在執行非同步任務之前的時候執行,並且是在主執行緒當中執行的,通常我們在這個方法裡做一些UI控制元件的初始化的操作,例如彈出一給ProgressDialog。

  • doInBackground(Params… params): 在onPreExecute()方法執行完之後,會馬上執行這個方法,這個方法就是來處理非同步任務的方法,Android作業系統會在後臺的執行緒池當中開啟一個worker thread來執行我們的這個方法,所以這個方法是在工作執行緒當中執行的,這個方法執行完之後就可以將我們的執行結果傳送給我們的最後一個 onPostExecute 方法,在這個方法裡,我們可以從網路當中獲取資料等一些耗時的操作。

  • onProgressUpdate(Progess… values): 這個方法也是在主執行緒當中執行的,我們在非同步任務執行的時候,有時候需要將執行的進度傳回給我們的UI介面,例如下載一張網路圖片,我們需要時刻顯示其下載的進度,就可以使用這個方法來更新我們的進度。在呼叫之前,我們要在 doInBackground 方法中呼叫publishProgress(Progress) 的方法來將我們的進度時刻傳遞給onProgressUpdate 方法來更新。

  • onPostExecute(Result… result): 當我們的非同步任務執行完之後,就會將結果傳回給這個方法,這個方法也是在UI Thread當中呼叫的,我們可以將傳回的結果顯示在UI控制元件上。

你可以不用說的這麼具體,但是如果你能把這一系列都答上來,那面試官應該是不會在繼續為難你。我在回答的時候還聊到了多執行緒可能發生記憶體洩露的問題,然後被面試官尷尬的說:“不用了不用了,我們繼續下一個問題。”其實我也不懂這算不算一種好事吧,但是總歸是你對整個知識體系的理解嘛。希望有大佬能指正什麼的。感恩!

三、View 及其他控制元件的使用和最佳化

其實在這一塊,主要還是按照你所做的專案的情況對你進行具體的提問,所以對你的專案所使用的框架、元件、控制元件必須要爛熟於心。知道為什麼使用它,怎麼使用它,使用它的結果是優化了些什麼?

  • 簡要闡述一下訊息分發機制吧?(All)

這也算是老生常談的一道基本是必考題了,可以先談談MotionEvent的幾種事件,分別在什麼條件下會發生。再談談分發的本質、傳遞順序、核心方法等,有一點非常關鍵,因為訊息分發機制是一個責任鏈樣式,所以在闡述的時候務必邏輯清晰。如果能結合原始碼的實現來談大概也會讓面試官好感Up。

  • MotionEvent 是手指觸控式螢幕幕產生的一系列事件。包含的事件有:

  • ACTION_DOWN:手指接觸螢幕

  • ACTION_MOVE:手指在螢幕上滑動

  • ACTION_UP:手指在螢幕上鬆開的一瞬間

  • ACTION_CANCEL:手指保持按下操作,並從當前控制元件轉移到外層控制元件時會觸發

    事件分發本質:就是對MotionEvent事件分發的過程。即當一個MotionEvent產生了以後,系統需要將這個點選事件傳遞到一個具體的View上。
    點選事件的傳遞順序:Activity(Window) -> ViewGroup -> View
    三個主要方法:

  • dispatchTouchEvent:進行事件的分發。傳回值是 boolean 型別,受當前onTouchEvent和下級view的dispatchTouchEvent影響.

  • onInterceptTouchEvent:對事件進行攔截。該方法只在ViewGroup中有,一旦攔截,則執行ViewGroup的onTouchEvent,在ViewGroup中處理事件,而不接著分發給View。且只呼叫一次,所以後面的事件都會交給ViewGroup處理。

  • onTouchEvent:進行事件處理。

    另外可以有選擇性的記錄兩段原始碼,分別是 view 和 viewGroup 的dispatchTouchEvent 方法。以下是 view 的 dispatchTouchEvent()函式的主要部分,主要是三個判斷條件的分析。

public boolean dispatchTouchEvent(MotionEvent event){
    ...//省略
    if(mOnTouchListener != null && (mViewFlags & ENABLED_MASK)==ENABLED && 
mOnTouchListener.onTouch(this,event)){
        return true;
    }
    return onTouchEvent(event);
}

以下是ViewGroup的dispatchTouchEvent()函式的主要部分:

public boolean dispatchTouchEvent(MotionEvent event){
   ...//省略
   if(disallowIntercept||!onInterceptTouchEvent(ev)){
       child.dispatchEvent()
}

你甚至可以和他談一談關於 disallowIntercept 去解決滑動衝突的問題。理論搭配原始碼,食用極佳。關於原始碼的分析很多大牛都寫過了,我也是抱著學習的態度。這裡就不貼出來了。

  • 滑動衝突事件應該怎樣去解決?(中廠、小廠)

這裡當時第一次被問到的時候確實沒有準備,之後看了釐米姑娘的面試題解(再次強力安利!)就回答上來了,以下直接貼小姐姐所寫的解答,可以在這個解答的基礎上加上一些自己的看法或者實際開發中遇到的類似問題。
(1)處理規則:
對於由於外部滑動和內部滑動方向不一致導致的滑動衝突,可以根據滑動的方向判斷誰來攔截事件。
對於由於外部滑動方向和內部滑動方向一致導致的滑動衝突,可以根據業務需求,規定何時讓外部View攔截事件何時由內部View攔截事件。
對於上面兩種情況的巢狀,相對複雜,可同樣根據需求在業務上找到突破點。
(2)實現方法:

  • 外部攔截法:指點選事件都先經過父容器的攔截處理,如果父容器需要此事件就攔截,否則就不攔截。具體方法:需要重寫父容器的 onInterceptTouchEvent 方法,在內部做出相應的攔截。

  • 內部攔截法:指父容器不攔截任何事件,而將所有的事件都傳遞給子容器,如果子容器需要此事件就直接消耗,否則就交由父容器進行處理。具體方法:需要配合requestDisallowInterceptTouchEvent 方法。

  • ListView 和 RecyclerView的選擇?為什麼?(中廠)

①佈局效果:RecyclerView支援線性佈局、網格佈局、瀑布佈局,可以控制橫向縱向滾動,從佈局效果上來看完爆ListView。
②基礎使用:ListView需要繼承重寫BaseAdapter類,自定義ViewHolder和重用ConvertView完成最佳化工作。
RecyclerView繼承重寫RecyclerView.Adapter和RecyclerView.ViewHolder,設定佈局管理器,控制整體佈局。規範化了ViewHolder,復用item也不需要像ListView一樣SetTag。
③空資料處理:ListView提供了setEmptyView這個API來處理Adapter中資料為空的情況,RecyclerView沒提供相應API。
④HeaderFooter:ListView提供了AddRemoveHeaderView方法解決,RecyclerView沒有提供。
⑤區域性掃清:ListView掃清notifyDataSetChanged()方法,區域性掃清需要自己實現。RecyclerView.Adapter則提供了notifyItemChanged()擁有更新單個itemView的掃清。
⑥動畫效果:Recycler輕鬆實現,listview需要自己寫屬性動畫,或者呼叫第三方庫
⑦監聽item事件:ListView專門提供了用於監聽item的回呼介面,RecyclerView提供的是addOnItemTouchListener。

  • ListView 怎麼進行最佳化的?(All)

①ConvertView重用機制:在getView()方法中使用ConvertView,不需要每次都inflate一個View出來,這樣既浪費時間又浪費記憶體。
②Viewholder:使用Viewholder,避免在getView()方法頻繁呼叫去使用findViewById方法,節省時間和記憶體。
③分頁載入:每載入一頁的資料就改寫上一頁的資料。
④資料中有圖片:使用第三方庫(三級快取機制)

  • ListView 是怎麼實現 RecycleBin 並更新 View 的?(大廠)

關於這道題,第一次被問及的時候確實沒回答上來,然後面試官就沖我笑笑:“嘿嘿沒讀過原始碼吧?”回去以後讀了郭霖大佬的部落格。做了以下記錄:
在 Adapter 中的 getView()方法中執行 LayoutInflater.inflate()方法是很消耗資源的,所以ListView透過 RecycleBin 去維護兩個陣列 mActiveViews 和mScrapViews 用來進行 view 的復用工作。具體是在繪製 view 的(measure->layout->draw)的 layout 過程中實現。
主要有三個步驟:
①ListView的children->RecycleBin
②ListView清空children
③Recyclebin->ListView的children
舉個例子,某一時刻ListView中顯示10個子View,position依次是0-9,這時下滑,ListView需要繪製下一幀,這時候ListView在layoutchildren方法中把這10個子View都存入了mActiveViews陣列中,然後清空children陣列,呼叫filldown方法,向listview中依次新增position 1到10的子view,在填充1-9時,由於在上一幀position=1-9的view已經被放入了mActiveViews陣列中,因此可以直接將其從陣列中取出,直接復用。如果沒能夠從mActivieViews中直接復用View,那麼就要呼叫obtainView方法獲取View,該方法嘗試間接復用RecycleBin中的mScrapViews中的View,如果不能間接復用,則建立新的View。這邊去看郭霖大神的文章:ListView工作原理完全解析。https://blog.csdn.net/guolin_blog/article/details/44996879

四、Java 基礎和計網基礎

  • 簡要介紹一下HashMap的實現原理?(中廠、小廠)

HashMap 基於 AbstractMap類,實現了Map、Cloneable(能被克隆)、Serializable(支援序列化)介面; 非執行緒安全;允許存在一個為null的key和任意個為null的value;採用連結串列雜湊的資料結構,即陣列和連結串列的結合;初始容量為16,填充因子預設為0.75,擴容時是當前容量翻倍,即2capacity。
1、Put方法的實現原理:比如hashMap.put(“Java”,0),先使用hash函式來確定這個Entry的插入位置,下標為Index。即index=hash(“java”),存入陣列下標為Index的地方,但是因為hashmap長度有限,插入的Entry越來越多,Index值會發生衝突,此時可以用連結串列的方式解決,透過頭插法,發生衝突時,插入對應的連結串列之中。
2、Get方法的實現原理,比如hashMap.get(“apple”),同樣對Key值做一次hash對映,算出其對應的index值,即Index = Hash(“apple”)這時從頭結點開始,一個個向下查詢,透過keys.equals()方法去找到連結串列中正確的節點。

  • HashMap 執行緒安全嗎?那如何保證其執行緒安全呢?(中廠)

可以簡單談談,使用 Hashtable 或者 ConcurrentHashMap。它們都可以用於多執行緒的環境,但是當 Hashtable 的大小增加到一定的時候,效能會急劇下降,因為迭代時需要被鎖定很長的時間。而 ConcurrentHashMap 引入了分割(segmentation),不論它變得多麼大,僅僅需要鎖定 map 的某個部分,而其它的執行緒不需要等到迭代完成才能訪問 map。簡而言之,在迭代的過程中,ConcurrentHashMap 僅僅鎖定 map 的某個部分,而 Hashtable 則會鎖定整個map。在 jdk1.8 之後,取消了segments 的方式,而是使用了transient volatile HashEntry[] table 的方式儲存資料,將陣列元素作為鎖,對每一行資料進行加鎖,減少了併發衝突的機率。由陣列+單向連結串列變為了陣列+單向連結串列+紅黑樹,將查詢的時間複雜度降至了O(logn)改進了一些效能。

  • HashMap 有序嗎?如何實現有序呢?(中廠)

HashMap 是無序的,而 LinkedHashMap 是有序的 HashMap,預設為插入順序,還可以是訪問順序,基本原理是其內部透過 Entry 維護了一個雙向連結串列,負責維護 Map的迭代順序。甚至可以深入的去談談 LinkHashMap 的底層實現機制。

  • 你知道哪些垃圾回收演演算法?(小廠)

四種主要的垃圾回收演演算法:
新生代:大批物件死去,只有少量存活。
①複製演演算法:把可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊,當這一塊用盡時,把還活著的物件複製到另一塊上,再將這塊給一次性清理掉。
老生代:物件存活率高,只需標記少量回收的物件。
②標記-清除演演算法:首先標記出要回收的物件,然後統一清除帶標記的物件。
③標記-整理演演算法:首先標記出要回收的物件,然後進行整理,使得存活的物件都向一端移動,直接清理掉端邊界以外的物件。
④分代收集演演算法,整合以上的特點,對新生代和老生代分別使用不同的回收演演算法。

  • TCP如何保證傳輸是可靠的?資料不丟失?連續且按序?(中廠)

①校驗和:在資料傳輸的過程中,將傳送的資料段當做一個16位整數,將整數加起來。進位補到最後,最後取反,得到校驗和。傳送方和接收方對比校驗和,一致不一定傳輸成功,但不一致一定失敗。
②確認應答與序列號:TCP傳輸時每個位元組的資料都編了號,這就是序列號seq。每當接收方接受到資料後,都會對傳輸方進行確認應答,也就是傳送ACK報文。這個ACK報文攜帶者對應的確認序列號,告訴傳送方,接收到了哪些資料,下次從哪裡發。
③超時重傳:兩種情況沒收到確認應答報文,A.資料丟包,接收方沒接到。B.ACK報文丟失。
傳送方會等待一段時間,沒收到ACK的情況下重發剛才傳送的資料包,如果是情況A,則接收方接到後回ACK,如果是第二種情況則接收方會發現重覆,丟棄資料但仍然傳送ACK。
④連線管理:三次握手、四次揮手,連線是傳輸的保證。
⑤流量控制:TCP根據接收端對資料的處理能力,決定傳送端的傳送速度,這個機制就是流量控制。在TCP協議的報頭資訊中,有一個16位的視窗大小,接收方在傳送確認應答ACK時,把自己的即時視窗大小填入,接收方根據這個即時視窗大小決定傳送的速度。如果為0,則停止傳送,等待一個超時重傳的時間,併傳送視窗偵測資料段,直到接收端更新這個視窗大小。
⑥擁塞控制:如果一開始就傳送大量的資料,那麼可能剛開始就很擁堵,incident引入了慢啟動的方式,先傳送少量的資料探路,定義擁塞視窗為1,每次收到ACK就+1,然後兩擁塞視窗和接受端的視窗大小進行比對,取較小的值作為實際傳送的視窗。

  • TCP、UDP有什麼區別?如果我要實現一個影片播放,我應該用TCP還是UDP,為什麼?(中廠)

TCP:傳輸控制協議,面向連線的,使用全雙工的可靠通道,提供可靠的服務,無差錯,不丟失,不重覆且按序到達。提供擁塞控制、流量控制、超時重發、丟棄重覆資料等等可靠性檢測手段,面向位元組流,僅支援一對一,用於傳輸可靠性要求高的資料。
UDP:使用者資料報協議,無連線的,使用不可靠通道。盡最大努力交付,不保證可靠交付,無擁塞控制等,面向報文,支援一對一、一對多、多對多的通訊,用於傳輸可靠性要求不高的資料。
UDP適合於對網路通訊質量要求不高,要求網路通訊速度儘量快的應用。TCP則適合於對網路通訊質量要求高,且可靠的應用。影片播放分為關鍵幀和普通幀,且是實時應用,丟失一些普通幀並不會有什麼影響,使用UDP更能保證其高效性和實時性。

  • get和post有什麼區別?(中廠)

Get:當客戶端要從伺服器中讀取某個資源時使用Get,一般用於獲取、查詢資源資訊,Get引數透過URL傳遞,傳遞的引數有長度限制,不能用來傳遞敏感資訊。
Post:當客戶端給伺服器提供資訊較多時可以使用Post,Post附帶有使用者資料,一般用於更新資源資訊,Post將請求引數封裝在HTTP請求資料中,可以傳輸大量資料,傳參方式也比Get更安全。

  • 談談TCP為什麼要三次握手、四次揮手?(中廠)

TCP建立連線時為了保證連線的可靠需要進行三次握手。客戶端向服務端傳送建立連線的SYN報文段,一旦包含SYN報文段的資料到達服務端,服務端從中提取出SYN報文段,為該TCP連線分配需要的快取和變數。並向客戶端傳送允許連線的報文段ACK以及報文段SYN,在收到報文段ACK之後,客戶端也要給連線分配需要的快取和變數,再傳送一個報文段ACK,表示確認。自此完成TCP連線。

由於TCP是全雙工的,因此兩方向需要單獨關閉。客戶端發起結束連線的資料段FIN,客戶端確認後傳送確認資料段ACK。此時結束了客戶端-服務端的連結。服務端再向客戶端傳送資料段FIN和資料段ACK,客戶端收到後回覆ACK資料段。自此雙方的連線正式結束。

  • 請求報文和響應報文的報文格式是怎樣的?(中廠)

啊啊啊講道理,以上問題都出自同一家公司。主要是他問到了我大學期間哪門課比較擅長,我說是計算機網路。所以可見,你說你擅長的,你就一定要對它很瞭解。改寫面要廣,深度也要夠。不然很容易被考倒,問題真的是一個接一個提出來。

  • 請求報文:
    <request-line> 請求行
    <essay-headers> 請求頭
    <blank line> 空格
    <request-body> 請求資料
    1.請求行:由請求方法欄位、URL欄位和HTTP協議版本欄位3個欄位組成,它們用空格分隔。例如,GET /index.html HTTP/1.1。
    2.請求頭部:由關鍵字/值對組成,每行一對,關鍵字和值用英文冒號“:”分隔。常見的請求頭:
    User-Agent:產生請求的瀏覽器型別。
    Accept:客戶端可識別的內容型別串列。
    Host:請求的主機名,允許多個域名同處一個IP地址,即虛擬主機。
    3.空行:最後一個請求頭之後是一個空行,發送回車符和換行符,通知伺服器以下不再有請求頭。
    4.請求資料:請求資料不在GET方法中使用,而是在POST方法中使用。POST方法適用於需要客戶填寫表單的場合。與請求資料相關的最常使用的請求頭是Content-Type和Content-Length。

  • 響應報文
    <status-line> 狀態行
    <essay-headers>  訊息報頭
    <response-body> 響應正文
    1.狀態行:
    HTTP-Version  Status-Code   Reason-Phrase   CRLF
    其中,HTTP-Version表示伺服器HTTP協議的版本;Status-Code表示伺服器發回的響應狀態程式碼;Reason-Phrase表示狀態程式碼的文字描述。
    狀態碼一般由三位數字組成,第一位數字表示相應的型別,常用的五大型別:
    ①1xx:表示伺服器已接受了客戶端請求,客戶端可繼續傳送請求。
    ②2xx:表示伺服器已接受了請求併進行處理。
    200 OK:表示客戶端請求成功。
    ③3xx:表示伺服器要求客戶端重定向。
    ④4xx:表示客戶端的請求有非法內容。
    400 Bad Request:表示客戶端請求有語法錯誤,不能被伺服器理解。
    401 Unauthonzed:表示請求未經授權,該狀態碼與WWW-Authenticate報頭域一起使用。
    403 Forbidden:表示伺服器接受到請求,但是拒絕提供服務,通常會在響應正文中給出不提供服務的原因。
    404 Not Found:請求的資源不存在,例如,收到了錯誤的url。
    ⑤5xx:表示伺服器未能正確處理客戶端的請求而產生意外錯誤。
    500 Internal Server Error:表示伺服器發生不可預期的錯誤,導致無法完成客戶端的請求。
    503 Service Unavailable:表示伺服器當前不能處理客戶端的請求,在一段時間之後,伺服器可能恢復正常。
    2.訊息頭部:如 Content-Type: text/html等。
    3.響應正文

  • 瞭解反射機制嗎?簡要的說一說?Class要獲取的幾種方式?(大廠、小廠)

Java 反射機制是在執行狀態下,對任意一個類,都能夠知道這個類的所有屬性和方法,對於任意一個物件,都能夠呼叫它的任意一個方法和屬性。這種動態獲取資訊的方式稱作反射機制。
①Object.getClass() (不適用於int、float等)
Car car = new Car();
Class clazz = car.getClass();
②.class標識 (用於未建立該類的實體時)
Class clazz = Car.class;
③Class.forName() (安卓中使用@hide註解隱藏起來的類)
Class clazz = Class.forName(com.example.test.Car);

  • 簡要的談談序列化的機制吧?(小廠)

序列化主要有兩大介面 Srializable 介面和 Parcelable 介面。序列化將一個物件轉換可儲存可運輸的狀態,可以透過網路進行傳遞,也可以儲存到本地。
應用場景:需要透過 Intent 和 Binder 等傳輸類的物件都需要完成物件的序列化過程。
①平臺不同:S 是 Java 的序列化介面,P 是 Android 的序列化介面
②序列化原理不同:S將物件轉換成可儲存可運輸的狀態,P將物件分解,分解後的每一個部分都是可傳遞的資料型別。
③優缺點:S簡單但是效率低,開銷大,序列化、反序列化需要大量的IO操作,P高效但是編寫麻煩。
④使用場景:S主要用於序列化到儲存裝置或者透過網路裝置傳輸,P主要用於記憶體的序列化。

  • 簡要的談談註解的理解?(大廠)

這個問題我確實還是處在知識的盲區,還需要不斷學習,這裡就推薦一篇文章吧。Java註解相關。https://blog.csdn.net/ClAndEllen/article/details/79392453

五、專案相關問題

以下的問題就是仁者見仁、智者見智了。主要是關於你專案的問題,所以你必須對你簡歷上的專案負責。保證他不問倒你,如果你也有用到以下的這些框架,或者這些控制元件。你也可以跟下來繼續看看。

  • 你的專案中提到你使用 ViewPager+Fragment 實現相應的介面,那你瞭解過ViewPager 的滑動是如何實現的嗎? (中廠)

ViewPager是一個容器類,直接繼承自ViewGroup,ViewPager主要就是根據重寫OnInterceptTouchEvent()與onTouchEvent()兩個事件分發的函式,而實現的手勢滑動。有兩種滑動方式:
1、在MOVE觸控事件中,頁面隨手指的拖動而移動。
2、在UP事件後,頁面滑動到指定頁面(透過Scroller實現的)
先來看第一種情況,onInterceptTouchEvent()主要作用就是判斷各種情況是不是在拖拽,是否要攔截此事件。在MOVE事件中,如果在拖拽,會呼叫performDrag()方法讓當前頁面移動。 performDrag()方法做了這麼幾件事:首先得到ViewPager需要滾動的距離,其次得到邊界條件leftBound和rightBound,根據邊界條件的約束得到真正的滾動距離,最後呼叫scrollTo()方法滾動到最終的位置。pageScrolled()簡單來說就是根據當前的滑動位置,找到當前的頁面資訊,然後得到viewpager滑動距離,最後呼叫了onPageScrolled(currentPage, pageOffset, offsetPixels)。首先獲得viewpager滑動過的距離比例,然後透過遍歷mItems快取串列,根據每個快取頁面的offset值得到該頁面的左右邊界,最後就是判斷viewpager滑動過的距離比例在哪一個快取頁面的邊界之內,這個快取頁面就是當前顯示的頁面。而如果viewpager顯示區域記憶體在兩個頁面顯示的時候,從快取串列的遍歷順序就可以看出,傳回的必然是最左邊的頁面。onPageScrolled()做了三件事:將DecorView顯示在螢幕中,不移除螢幕、回呼介面onPageScrolled()方法、回呼介面的transformPage()方法,自定義實現頁面轉換動畫。
簡單總結下,就是在onInterceptTouchEvent()方法中根據不同情況對mIsBeingDragged進行賦值,對觸控事件是否進行攔截;如果在MOVE事件中是可滑動的,就呼叫performDrag()讓檢視跟著滑動,當然此方法中是呼叫scrollTo()方法形成拖拽效果,接著呼叫pageScrolled()對DecorView固定顯示,回呼介面,回呼轉換動畫介面。
再來看第二種情況,另外一種移動方式在onTouchEvent()的UP事件中,呼叫setCurrentItemInternal()對平滑滑動進行處理,透過最後呼叫smoothScrollTo()方法,利用Scroller達到目的,當然最後也呼叫了pageScrolled()進行介面的回呼等操作,在滑動結束的最後,呼叫completeScroll(boolean postEvents)完成滑動結束後的相關清理工作。
情況一:onInterceptTouchEvent()—>賦值mIsBeingDragged,判斷是否攔截。—>performDrag()->ScrollTo()->pageScrolled()->onPageScrolled()->DecorView固定顯示,回呼介面,回呼轉換動畫介面。
情況二:onTouchEvent()->UP事件->計算下一個應該顯示的nextPage->setCurrentItemInternal()->smoothScrollTo()方法,利用Scroller達到目的 ->startScroll()->computeScroll()不斷的重繪->completeScroll()完成滑動結束後的相關清理工作。

  • ViewPager載入Fragment時,使用什麼配接器?有什麼區別呢?(大廠)

兩種配接器,FragmentPagerAdapter 和 FragmentStateAdapter。前者類內的每一個生成的 Fragment 都將儲存在記憶體之中,因此適用於那些相對靜態的頁,數量也比較少的場景。如果需要處理有很多頁,並且資料動態性較大、佔用記憶體較多的情況,這時候,就需要用到後者。後者會把已經建立的Fragment進行儲存。

  • 你的專案中使用了Fragment,那在一個Fragment裡面開啟另一個Fragment(巢狀Fragment)要註意什麼呢?(大廠)

在Fragment中巢狀Fragment時,一定要使用getChildFragmentManager();否則,會在ViewPager中出現fragment不會載入的情況,即fragment出現空白頁的情況。

  • 你的專案有沒有進行相應的效能最佳化呢?從幾個方面具體講講怎麼做到的吧?(All)

關於這個問題有一些想說的,如果面試官詢問你專案中有遇到什麼難點,或者有什麼你解決的不錯的事情時,你也可以把這一塊嘗試著回答。畢竟最佳化對於一個移動應用開發是極其關鍵的。主要的方向是:流暢性、穩定性、資源節省性。畢竟是使用者留存率的關鍵嘛。一個最佳化好的APP總是能提高其使用者留存率,經常ANR的軟體你巴不得解除安裝了他!主要從以下幾個角度去切入主題:
1、佈局最佳化:核心是減少層級:
①多巢狀情況下盡可能使用RelativeLayout減少巢狀。
②佈局層級相同的情況下儘量使用LinearLayout(更加高效)。
③儘量使用
標簽重用佈局,標簽來減少層級,標簽進行懶載入。
④使用佈局調優工具,如AndroidStudio自帶的Hierarchy Viewer,可以檢視佈局和繪製的時間。
⑤儘量少使用wrap_content這種測量耗時較長的屬性。
2、繪製最佳化:①降低View.onDraw()的複雜度。不要在onDraw()中建立新的區域性物件,因為onDraw會被頻繁的呼叫。避免在onDraw()中執行大量&耗時的操作。
②避免過度繪製OverDraw,移除不必要的控制元件背景。減少佈局的層級。
3、響應速度最佳化:ANR(應用程式無響應),應用在5秒內未相應使用者的輸入事件。廣播接收器10秒內未完成相關的處理。服務20秒內無法處理完成。最佳化方案:使用多執行緒,把大量&耗時的工作放在工作執行緒中處理。比如AsyncTask、繼承自Thread類,實現Runnable介面,Handler訊息機制,HandlerThread進行非同步訊息傳遞等。 當發生ANR時,可以透過data/anr找到traces.txt檔案確定ANR發生的原因。
4、記憶體最佳化:核心問題是記憶體洩露的問題:在申請記憶體後,不需使用時無法釋放的現象,是導致記憶體上限溢位(程式所需的記憶體>所分配的記憶體資源)的原因。
①集合類,集合類新增元素後,仍取用著集合元素物件,導致集合元素物件不可被回收。
透過集合類的clear()方法&設定為null方式解決。
②Static關鍵字修飾的成員變數(單例樣式):由於取用耗費資源過多的實體(比如context)導致該成員變數的生命週期>取用實體的生命週期,導致實體無法被回收。
透過:
A、儘量避免Static成員變數取用消耗資源過多的實體(如Context),假設需要取用Context可以使用Application的Context。
B、使用弱取用代替強取用去持有實體。
③非靜態內部類/匿名類:由於非靜態內部類所建立的實體是靜態的,會因非靜態內部類預設持有外部類的取用而導致外部類無法被釋放。
透過將非靜態內部類設定為靜態內部類,儘量避免費靜態內部類所建立的實體=靜態,或者將內部類抽取出來封裝成單例。
④多執行緒:多執行緒大都是非靜態內部類/匿名類,持有外部類的取用導致無法被銷毀。或是執行緒的生命週期>Activity這種外部類的生命週期。
透過1、在Activity這種外部類的生命週期的onDestroy函式中強制關閉執行緒,或是將非靜態內部類該為靜態內部類,使取用關係不存在。
⑤資源物件使用後未關閉:比如廣播、檔案、資料庫遊標、圖片資源未及時關閉、登出。透過在Activity的onDestroy()方法中呼叫相應的登出、關閉方法將其關閉或登出。
5、Bitmap最佳化:①使用完畢後,透過軟取用釋放掉圖片資源。②根據解析度適配&縮放圖片。A.設定對應的圖片資源(hdpi,xxhdpi,xhdpi)等。B.透過decodeResource()方法適配③選擇合適的解碼方式 ④設定圖片快取機制,如A.三級快取機制(記憶體快取-本地快取-網路快取)B.使用軟取用的方式(記憶體空間不足時,才回收這些物件的記憶體)

  • 你說你專案中使用了OkHttp框架,你可以說一下你為什麼要使用該框架呢?->延伸,他的內部的攔截器機制是怎麼實現的呢?(All)

關於框架類的題目,如果你體現在簡歷上,是一定會被問到的。所以一切要理解清楚,有時間還要去研讀一下他的具體實現機制。面試官是很有可能會一路小跑問到底層。這裡我們來聊聊OkHttp的內部攔截器機制以及OkHttp發起一次請求的過程。

建立一個 OkHttpClient(單例樣式),重覆建立 OkHttpClient 記憶體可能會爆掉,建立 request物件(建造者樣式),初始化請求方式(Get/Post),請求頭(Header)以及請求的連結地址(url)。根據request物件,透過OkHttpClient的newCall()方法建立一個RealCall物件。判斷是同步(execute)非同步(enqueue) ,如果是非同步則透過Dispatcher。RealCall透過執行getResponseWithInterceptorChain()傳回Response。

OkHttp請求的流程

okHttp的攔截器是一個責任鏈樣式,真正的執行網路請求和傳回相應結果是透過函式getResponseWithInterceptorChain()實現,透過實現Interceptor.Chain,並且執行了Chain.proceed()方法在攔截器間傳遞,責任鏈中每個攔截器都會執行chain.proceed()方法之前的程式碼,等責任鏈最後一個攔截器執行完畢後會傳回最終的響應資料,而chain.proceed() 方法會得到最終的響應資料,這時就會執行每個攔截器的chain.proceed()方法之後的程式碼,其實就是對響應資料的一些操作。當責任鏈執行完畢,如果攔截器想要拿到最終的資料做其他的邏輯處理等,這樣就不用在做其他的呼叫方法邏輯了,直接在當前的攔截器就可以拿到最終的資料。
五大攔截器的主要順序是:
1.負責失敗重試以及重定向的 RetryAndFollowUpInterceptor。
2.負責把使用者構造的請求轉換為傳送到伺服器的請求、把伺服器傳回的響應轉換為使用者友好的響應的BridgeInterceptor。
3.負責讀取快取直接傳回、更新快取的CacheInterceptor。
4.負責和伺服器建立連線的ConnectInterceptor。
5.負責向伺服器傳送請求資料、從伺服器讀取響應資料的CallServerInterceptor。

攔截器順序流程

其他的除了純技術實現角度,你還可以聊一聊具體在專案中使用這個框架遇到的坑(比如怎麼搞的記憶體又爆了,可能會引起共鳴,或者讓面試官確認你確實使用過這個框架,有個實際操作,而不是純粹的背知識點,這就和回答某種卷子的理論聯絡材料中是一個道理),或者說這個框架主要幫助你專案的點,以一個聊天的形式進行,我覺得就是很良好的面試心態了。

  • 你說你專案中使用了Glide框架,說說你為什麼要使用這個框架呢?->延伸:它的三級快取機制是如何實現的呢?->你瞭解LRU快取演演算法嗎?->你瞭解LinkHashMap嗎?->它是怎麼實現的呢?(大廠)

大廠很多情況下不僅考察知識的廣度、也考察知識的深度,因此可能會環環相扣,成為一個類責任鏈樣式的問題鏈樣式,所以如果你能夠搶先一步,在他陳述這個問題最淺顯的角度時,層層展開,完成知識的深度優先遍歷。面試官的好感應該是up的。同時也是理論聯絡實際,可以談談實際開發遇到的一些問題。怎麼使用的?

這裡我們討論一下,在使用Glide載入圖片時with()方法有兩種情況,傳入Application型別的引數,和傳入非Application型別的引數,那麼如果是後者,如何保證Activity被關閉掉時,Glide也停止進行圖片載入呢?

在Glide的With()方法中傳回的是一個RequestManager物件可以看出,在傳入非Application型別的引數情況下,不管是Activity還是Fragment,都會向當前的Activity中新增一個隱藏的Fragment,可是Glide並沒有辦法知道Activity的生命週期,於是Glide就使用了新增隱藏Fragment的這種小技巧,因為Fragment的生命週期和Activity是同步的,如果Activity被銷毀了,Fragment是可以監聽到的,這樣Glide就可以捕獲這個事件並停止圖片載入了。而如果是第一種情況,傳入的是Application型別的引數,那麼Glide的生命週期就和應用程式的週期同步,應用程式關閉,Glide也停止。  另外,如果如果我們是在非主執行緒當中使用的Glide,那麼不管你是傳入的Activity還是Fragment,都會被強制當成Application來處理。 詳情見郭霖大神的文章詳解Glide內部實現機制還。https://blog.csdn.net/sgiceleo/article/details/64440783 還是需要認真的去研讀內部實現的。

  • 你專案中有遇到什麼問題嗎?怎麼解決的?

關於這個問題,我回答了ListView載入圖片時出現的亂序問題,這個問題當時也困擾了我很久,圖片怎麼在亂閃!亂載入!所以,實際開發時遇到的問題一定要記下來!!!!
每當有新的元素進入介面時就會回呼getView()方法,而在getView()方法中會開啟非同步請求從網路上獲取圖片,因為網路操作都是比較耗時的, 所以某一個位置上的元素進入螢幕後開始從網路上請求圖片,但是還沒等圖片下載完成,它就又被移出了螢幕。這種情況下根據ListView的工作原理,被移出螢幕的控制元件將會很快被新進入螢幕的元素重新利用起來,而如果在這個時候剛好前面發起的圖片請求有了響應,就會將剛才位置上的圖片顯示到當前位置上,因為雖然它們位置不同,但都是共用的同一個ImageView實體,這樣就出現了圖片亂序的情況。但是還沒完,新進入螢幕的元素它也會發起一條網路請求來獲取當前位置的圖片,等到圖片下載完的時候會設定到同樣的ImageView上面,因此就會出現先顯示一張圖片,然後又變成了另外一張圖片的情況。
有以下幾種解決方案:
①使用findViewByTag。由於findViewByTag()方法需要有ListView的實體。恰好在getView()方法中的第三個引數就是ListView的實體,並且在getView()中呼叫ImageView的setTag()方法,並將當前圖片的Url傳進去,併在onpostExcute()裡透過ListView的findViewByTag方法去獲取ImageView實體,判斷是否為空,不為空就填入圖片。原因是getView方法呼叫時,setTag會改寫舊的Url,如果這時候呼叫findViewBytag如果傳的是舊的url則傳回就是null,我們又規定只有不會Null時才會設定圖片,因此解決這個問題。
②使用雙向弱取用關聯。BitmapWorkerTask指向ImageView:在BitmapWorkerTask中加一個建構式,併在建構式中要求ImageView這個引數。不過不是直接持有ImageView的取用,而是用WeakReference對ImageView進行一層封裝。
ImageView指向BitmapWorkerTask:藉助自定義Drawable的方式來實現。自定義一個AsyncDrawble類繼承自BitmapDrawable,然後重寫其建構式,在建構式中要求把BitmapWorkerTask傳入,並包裝一層弱取用。併在getView()方法中呼叫imageView.setImageDrawable()方法把AsyncDrawable設定進去,如此便關聯完成。
透過getAttachImageView()方法和getBitmapWorkerTask()方法,如果獲取到的BitmapWorkerTask等於this,那麼就傳回ImageView,否則傳回null。
③使用NetWorkImageView。如果控制元件已經被移出了螢幕且被重新利用了,那麼就把之前的請求取消掉。由於Volley在網路方面的封裝非常優秀,它可以保證只要是取消掉的執行緒,就絕對不會進行回呼。

六、演演算法與資料結構、設計樣式

Talk is cheap.Show me the Code.
這一塊毫無疑問是我相對比較薄弱的地方了!
手寫演演算法。有時間限制的情況下。
在刷的題比較少的條件下。
確實還是有壓力的。
慢慢學習吧!(LeetCode&劍指Offer)
先留著坑把碰到的題目都擺上來。
演演算法篇容我整理一下週末見。

  • 最長迴文子串問題?(大廠)

我給他寫了暴力..他問我有時間複雜度上的改進嗎…然後在他的指引下又寫了中心波紋….然後…其實我是聽了同學聊到馬拉車演演算法的線性的。但是具體實現還沒有看到…就只能跟他闡述了一下..也沒有為難我…

  • 給字元陣列,去除字串間多餘的空格且將每個字串首字母大寫,不開闢新空間。(大廠)

  • 給定字串,要求翻轉字串裡面的單詞,但不改變字串單詞的順序(大廠)。

關於這題我本來是覺得不能使用String的Split()和Reverse()函式的,但是先寫在紙上試探了一下,沒想到他說可以,那這題..就…有點太…簡單了..

  • 大數乘法(不能使用BigInteger類)(大廠)

這題不能用自帶類的情況下,就是用陣列存放乘法的區域性結果之後再進位解決了。

  • 給定數字n,1..n分佈在二叉搜尋樹上,問有多少獨一無二的二叉搜尋樹?

卡特蘭數問題…當時確實有時間壓力沒有解出來,然後就涼涼了。(大廠)

  • 快速排序(小廠)

這沒什麼好說的了,小廠大部分還是…比較尊重基礎的….

  • 二分排序(小廠)

其實從題目上來看,就很明顯看出大小廠關於資料結構與演演算法要求的不同了…..

贊(0)

分享創造快樂