大廠OPPO面試— Android 開發技術面總結

最近互聯網寒冬,工做真的很差找嗎?前兩天還在看一個帖子:裁人浪潮+寒冬大逃殺,互聯網人該何去何從?,感受做爲移動端技術人員,就目前而言,轉行須要學習新的技術,還須要從新開始,不如進階技術,學好Android,走向大廠。html

原本着 做者丨AffyFei ,原文地址:https://www.jianshu.com/p/b110f9c1384cjava

今天早上參加了深圳 OPPO 開發工程師的技術面試,總的來講面試過程不是很順利。面試官並無問一些很深奧的底層原理,基本都是一些 Java 基礎以及 Android 四大組件內的基礎,可是我自身在開發過程當中並無很重視這些理論基礎,致使不少知識點都忘記了。整個面試過程耗時一小時,感謝兩位面試官不厭其煩地給我提示,一方面讓我可以回想起來那些遺忘的知識點,另外一方面也緩解了尷尬的氣氛。。。android

順便一說,OPPO 的保密工做仍是作得比較嚴格的,進去後海卓越中心大樓前須要申請臨時通行證才能進去。而在面試前還須要登記,而且把手機的先後攝像頭都給用膠帶封起來才能進行面試。廢話少說,下面分紅兩部分彙總一下此次技術面試的知識點。面試

Java

一、如何理解Java的多態?其中,重載和重寫有什麼區別?

多態是同一個行爲具備多個不一樣表現形式或形態的能力,多態是同一個接口,使用不一樣的實例而執行不一樣操做,多態就是程序運行期間才肯定,一個引用變量倒底會指向哪一個類的實例對象,該引用變量發出的方法調用究竟是哪一個類中實現的方法。sql

多態存在的三個必要條件是:繼承,重寫,父類引用指向子類引用。數據庫

多態的三個實現方式是:重寫,接口,抽象類和抽象方法。設計模式

重寫(Override)和重載(Overload)的區別api

二、談一下JVM內存區域劃分?哪部分是線程公有的,哪部分是私有的?

JVM 的內存區域能夠分爲兩類:線程私有和區域和線程共有的區域。 線程私有的區域:程序計數器、JVM 虛擬機棧、本地方法棧;線程共有的區域:堆、方法區、運行時常量池。android-studio

**程序計數器,也有稱做PC寄存器。**每一個線程都有一個私有的程序計數器,任什麼時候間一個線程都只會有一個方法正在執行,也就是所謂的當前方法。程序計數器存放的就是這個當前方法的JVM指令地址。當CPU須要執行指令時,須要從程序計數器中獲得當前須要執行的指令所在存儲單元的地址,而後根據獲得的地址獲取到指令,在獲得指令以後,程序計數器便自動加1或者根據轉移指針獲得下一條指令的地址,如此循環,直至執行完全部的指令。網絡

**JVM虛擬機棧。**建立線程的時候會建立線程內的虛擬機棧,棧中存放着一個個的棧幀,對應着一個個方法的調用。JVM 虛擬機棧有兩種操做,分別是壓棧和出站。棧幀中存放着局部變量表(Local Variables)、操做數棧(Operand Stack)、指向當前方法所屬的類的運行時常量池的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些額外的附加信息。

**本地方法棧。**本地方法棧與Java棧的做用和原理很是類似。區別只不過是Java棧是爲執行Java方法服務的,而本地方法棧則是爲執行本地方法(Native Method)服務的。在JVM規範中,並無對本地方發展的具體實現方法以及數據結構做強制規定,虛擬機能夠自由實現它。在HotSopt虛擬機中直接就把本地方法棧和Java棧合二爲一。

**堆。**堆是內存管理的核心區域,用來存放對象實例。幾乎全部建立的對象實例都會直接分配到堆上。因此堆也是垃圾回收的主要區域,垃圾收集器會對堆有着更細的劃分,最多見的就是把堆劃分爲新生代和老年代。java堆容許處於不連續的物理內存空間中,只要邏輯連續便可。堆中若是沒有空間完成實例分配沒法擴展時將會拋出OutOfMemoryError異常。

**方法區。**方法區與堆同樣全部線程所共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、及時編譯器編譯後的代碼等數據。在Class文件中除了類的字段、方法、接口等描述信息外,還有一項信息是常量池,用來存儲編譯期間生成的字面量和符號引用。

其實除了程序計數器,其餘的部分都會發生 OOM。

堆。 一般發生的 OOM 都會發生在堆中,最多見的可能致使 OOM 的緣由就是內存泄漏。

**JVM虛擬機棧和本地方法棧。 **當咱們寫一個遞歸方法,這個遞歸方法沒有循環終止條件,最終會致使 StackOverflow 的錯誤。固然,若是棧空間擴展失敗,也是會發生 OOM 的。

**方法區。**方法區如今基本上不太會發生 OOM,但在早期內存中加載的類信息過多的狀況下也是會發生 OOM 的。

三、final關鍵字的用法?

final 能夠修飾類、變量和方法。修飾類表明這個類不可被繼承。修飾變量表明此變量不可被改變。修飾方法表示此方法不可被重寫 (override)。

四、死鎖是怎麼致使的?如何定位死鎖

某個任務在等待另外一個任務,然後者又等待別的任務,這樣一直下去,直到這個鏈條上的任務又在等待第一個任務釋放鎖。這獲得了一個任務之間互相等待的連續循環,沒有哪一個線程能繼續。這被稱之爲死鎖。當如下四個條件同時知足時,就會產生死鎖:

  • (1) 互斥條件。任務所使用的資源中至少有一個是不能共享的。

  • (2) 任務必須持有一個資源,同時等待獲取另外一個被別的任務佔有的資源。

  • (3) 資源不能被強佔。

  • (4) 必須有循環等待。 一個任務正在等待另外一個任務所持有的資源,後者又在等待別的任務所持有的資源,這樣一直下去,直到有一個任務在等待第一個任務所持有的資源,使得你們都被鎖住。

要解決死鎖問題,必須打破上面四個條件的其中之一。在程序中,最容易打破的每每是第四個條件。

關於如何手寫死鎖和定位方法,可參考這篇博客。

https://blog.csdn.net/Andy_96/article/details/82812538

五、數據庫如何進行升級?SQLite增刪改查的基礎sql語句?

/**
     * Create a helper object to create, open, and/or manage a database.
     * This method always returns very quickly.  The database is not actually
     * created or opened until one of {@link #getWritableDatabase} or
     * {@link #getReadableDatabase} is called.
     *
     * @param context to use to open or create the database
     * @param name of the database file, or null for an in-memory database
     * @param factory to use for creating cursor objects, or null for the default
     * @param version number of the database (starting at 1); if the database is older,
     *     {@link #onUpgrade} will be used to upgrade the database; if the database is
     *     newer, {@link #onDowngrade} will be used to downgrade the database
     */
    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
        this(context, name, factory, version, null);
    }

    public SQLiteDatabase getWritableDatabase() {
        synchronized (this) {
            return getDatabaseLocked(true);
        }
    }

  private SQLiteDatabase getDatabaseLocked(boolean writable) {
      .......
      db.beginTransaction();
      try {
              if (version == 0) {
                   onCreate(db);
              } else {
                   if (version > mNewVersion) {
                         onDowngrade(db, version, mNewVersion);
                   } else {
                         onUpgrade(db, version, mNewVersion);
                   }
              }
               db.setVersion(mNewVersion);
                db.setTransactionSuccessful();
              } finally {
                 db.endTransaction();
              }
  }

在 SQLiteOpenHelper 的構造函數中,包含了一個 version 的參數。這個參數便是數據庫的版本。 因此,咱們能夠經過修改 version 來實現數據庫的升級。 當version 大於原數據庫版本時,onUpgrade()會被觸發,能夠在該方法中編寫數據庫升級邏輯。具體的數據庫升級邏輯示例可參考這裏https://blog.csdn.net/s003603u/article/details/53942411

經常使用的SQL增刪改查:
  • :INSERT INTO table_name (列1, 列2,…) VALUES (值1, 值2,….)
  • : DELETE FROM 表名稱 WHERE 列名稱 = 值
  • :UPDATE 表名稱 SET 列名稱 = 新值 WHERE 列名稱 = 某值
  • :SELECT 列名稱(通配是*符號) FROM 表名稱

ps:操做數據表是:ALTER TABLE。該語句用於在已有的表中添加、修改或刪除列。

ALTER TABLE table_name ADD column_name datatype ALTER TABLE table_name DROP COLUMN column_name

Android

一、Broadcast的分類?有序,無序?粘性,非粘性?本地廣播?

廣播能夠分爲有序廣播、無序廣播、本地廣播、粘性廣播。其中無序廣播經過sendBroadcast(intent)發送,有序廣播經過sendOrderedBroadcast(intent)發送。

有序廣播

  • (1) 有序廣播能夠用priority來調整優先級 取值範圍-1000~+1000,默認爲0,數值越大優先級越高,優先級越高越優先得到廣播響應。

  • (2) abortBroadcast()可來終止該廣播的傳播,對更低優先級的屏蔽,注意只對有序廣播生效。

  • (3) 有序廣播在傳播數據中會發生好比setResultData(),getResultData(),在傳播過程當中,能夠重新設置數據

關於本地廣播,能夠查看這篇文章 http://www.trinea.cn/android/localbroadcastmanager-impl/

總的來講,本地廣播是經過LocalBroadcastManager內置的Handler來實現的,只是利用了IntentFilter的match功能,至於BroadcastReceiver 換成其餘接口也無所謂,順便利用了現成的類和概念而已。在register()的時候保存BroadcastReceiver以及對應的IntentFilter,在sendBroadcast()的時候找到和Intent對應的BroadcastReceiver,而後經過Handler發送消息,觸發executePendingBroadcasts()函數,再在後者中調用對應BroadcastReceiver的onReceive()方法。

粘性消息:粘性消息在發送後就一直存在於系統的消息容器裏面,等待對應的處理器去處理,若是暫時沒有處理器處理這個消息則一直在消息容器裏面處於等待狀態,粘性廣播的Receiver若是被銷燬,那麼下次重建時會自動接收到消息數據。(在 android 5.0/api 21中deprecated,再也不推薦使用,相應的還有粘性有序廣播,一樣已經deprecated)

二、Android中的事件傳遞機制?

當咱們的手指觸碰到屏幕,事件是按照Activity->ViewGroup->View這樣的流程到達最終響應觸摸事件的View的。而在事件分發過程當中,涉及到三個最重要的方法:dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent。咱們的手指觸摸到屏幕的時候,會觸發一個Action_Down類型的事件,當前頁面的Activity會首先作出相應,也就是說會走到Activity的dispatchTouchEvent()方法內。在這個方法內部有下面兩個邏輯:

調用getWindow.superDispatchTouchEvent()。

若是上一步返回true,則直接返回true;不然return本身的onTouchEvent()。顯然,當getWindow.superDispatchTouchEvent()返回true,表示當前事件已經被消費掉,無需調用onTouchEvent;不然表明事件並無被處理,所以須要調用Activity的onTouchEvent進行處理。

咱們都知道,getWindow()返回的是PhoneWindow,所以這句代碼本質上調用了PhoneWindow中的superDispatchTouchEvent()。然後者實際上調用了mDecor.superDispatchTouchEvent(event)。這個mDecor也就是DecorView,它是FrameLayout的一個子類。在DecorView中的superDispatchTouchEvent(event)中調用的是super.dispatchTouchEvent()。所以,本質上調用的是ViewGroup的dispatchTouchEvent()。

到這裏,事件已經從Activity傳遞到ViewGroup了。接下來咱們分析ViewGroup。

在ViewGroup的dispatchTouchEvent()中邏輯大體以下:

經過onInterceptTouchEvent()判斷當前ViewGroup是否攔截,默認的ViewGroup都是不攔截的;

若是攔截,則return本身的onTouchEvent();

若是不攔截,則根據child.dispatchTouchEvent()的返回值判斷。若是返回true,則return true;不然return自身的onTouchEvent(),在這裏實現了未處理事件的向上傳遞。

一般狀況下,ViewGroup 的 onInterceptTouchEvent() 都返回 false,表示不攔截。這裏須要注意的是事件序列,好比Down事件、Move事件…Up事件,從 Down到 Up 是一個完整的事件序列,對應着手指從按下到擡起這一系列事件,若是ViewGroup 攔截了 Down 事件,那麼後續事件都會交給這個 ViewGroup 的onTouchEvent。若是 ViewGroup 攔截的不是 Down 事件,那麼會給以前處理這個Down 事件的 View發送一個Action_Cancel 類型的事件,通知子View這個後續的事件序列已經被 ViewGroup 接管了,子 View 恢復以前的狀態便可。

這裏舉一個常見的例子:在一個 Recyclerview 中有不少的 Button,咱們首先按下了一個 button,而後滑動一段距離再鬆開,這時候 Recyclerview 會跟着滑動,並不會觸發這個 button 的點擊事件。這個例子中,當咱們按下 button 時,這個 button 接收到了 Action_Down 事件,正常狀況下後續的事件序列應該由這個 button處理。但咱們滑動了一段距離,這時 Recyclerview 察覺到這是一個滑動操做,攔截了這個事件序列,走了自身的 onTouchEvent()方法,反映在屏幕上就是列表的滑動。而這時 button 仍然處於按下的狀態,因此在攔截的時候須要發送一個 Action_Cancel 來通知 button 恢復以前狀態。

事件分發最終會走到View的dispatchTouchEvent()中。在View的dispatchTouchEvent()中沒有onInterceptTouchEvent(),這裏很容易理解,View沒有child,也就不存在攔截。View的dispatchTouchEvent()直接return了本身的onTouchEvent()。若是onTouchEvent()返回true表明事件被消費,不然未消費的事件會向上傳遞,直到有View處理了事件或一直沒有消費,最終回到Activity的onTouchEvent()終止。

有時候會有人混淆onTouchEvent和onTouch。首先,這兩個方法都在View的dispatchTouchEvent()中:

若是touchListener不爲null,而且這個View是enable的,並且onTouch返回true,都知足時直接return true,走不到onTouchEvent()方法。

不然,就會觸發onTouchEvent()。所以onTouch優先於onTouchEvent得到事件處理權。

最後附上流程圖總結:

image

touch事件傳遞流程

參考: https://juejin.im/entry/58df5b33570c35005798493c http://www.javashuo.com/article/p-rqknmrxh-a.html

三、Handler的原理?

與Handler密切相關的還有Message、MessageQueue、Looper。

Message。Message有兩個關鍵的成員變量:target、callback:

(1) target。就是發送消息的Handler

(2) callback。調用Handler.post(Runnable)時傳入的Runnable類型的任務。post事件的本質也是建立了一個Message,將咱們傳入的這個runnable賦值給建立的Message的callback這個成員變量。

MessageQueue。消息隊列用於存放消息,其中重點關注next()方法,它會返回下一個待處理的消息。

Looper。Looper消息輪詢器實際上是鏈接Handler和消息隊列的核心。想要在一個線程中建立一個Handler,首先要經過Looper.prepare()建立Looper,以後還得調用Looper.loop()開啓輪詢。

(1) prepare()。這個方法作了兩件事:首先經過ThreadLocal.get()獲取當前線程中的Looper,若是不爲空則拋出RuntimeException。不然建立Looper,並經過ThreadLocal.set(looper)將當前線程與剛剛建立的Looper綁定。值得注意的是,上面的消息隊列的建立其實就是發生在Looper的構造函數中。

(2)loop()。這個方法開啓了整個事件機制的輪詢。其本質是開啓一個死循環,不斷地經過MessageQueue的next()方法獲取消息msg。拿到消息後會調用msg.target.dispatchMessage()來作處理。綜上也就是調用handler.dispatchMessage()。

Handler。Handler重點在於發送消息和處理消息。

(1)發送消息。其實發送消息除了 sendMessage 以外還有 sendMessageDelayed 和 post 以及 postDelayed 等等不一樣的方式。但它們的本質都是調用了 sendMessageAtTime。在 sendMessageAtTime 這個方法中調用了 enqueueMessage。在 enqueueMessage 這個方法中作了兩件事:經過 msg.target = this 實現了消息與當前 handler 的綁定。而後經過 queue.enqueueMessage 實現了消息入隊。

(2)處理消息。 消息處理的核心其實就是dispatchMessage()這個方法。這個方法裏面的邏輯很簡單,先判斷 msg.callback 是否爲 null,若是不爲空則執行這個 runnable。若是爲空則會執行咱們的handleMessage方法。

四、ANR出現的狀況有幾種? 怎麼分析解決ANR問題?

ANR(Application Not responding)。Android中,主線程(UI線程)若是在規定時內沒有處理完相應工做,就會出現ANR。具體來講,ANR會在如下幾種狀況中出現:

(1) 輸入事件(按鍵和觸摸事件)5s內沒被處理

(2) BroadcastReceiver的事件(onRecieve方法)在規定時間內沒處理完(前臺廣播爲10s,後臺廣播爲60s)

(3) service 前臺20s後臺200s未完成啓動

(4) ContentProvider的publish在10s內沒進行完

分析ANR問題,須要結合Log以及trace文件。具體分析流程,可參照如下兩篇文章:

https://www.jianshu.com/p/fa962a5fd939 https://blog.csdn.net/droyon/article/details/51099826

五、內存泄露的場景有哪些?內存泄漏分析工具使用方法?
  • 常見的內存泄露有:

  • 單例模式引發的內存泄露。

  • 靜態變量致使的內存泄露。

  • 非靜態內部類引發的內存泄露。

  • 使用資源時,未及時關閉引發內存泄露。

  • 使用屬性動畫引發的內存泄露。

  • Webview致使的內存泄露。

而對於內存泄露的檢測,經常使用的工具備LeakCanary、MAT(Memory Analyer Tools)、Android Studio自帶的Profiler。關於用法,網上教程不少,可自行查閱,下面兩個經供參考:

三種用法https://blog.csdn.net/qq_20280683/article/details/7796420八、MAThttps://blog.csdn.net/yibanbubadao123/article/details/51871733

同時附上官方Android Profiler教程https://developer.android.com/studio/profile/android-profiler?utm_source=android-studio

六、如何實現啓動優化,有什麼工具可使用?

重點提到了systrace這個工具,詳細用法能夠參考下面幾篇文章:

https://blog.csdn.net/Kitty_Landon/article/details/79192377 https://www.cnblogs.com/baiqiantao/p/7700511.html https://blog.csdn.net/xiyangyang8/article/details/50545707 https://blog.csdn.net/cxq234843654/article/details/74388328

七、經常使用的設計模式有哪些?是否瞭解責任鏈模式?

單例模式,觀察者模式,工廠模式,建造者模式,構造者模式,中間者模式,橋接模式,適配器模式等等。

總結

如今回顧一下,問的問題並不難,只是環環相扣問出了不少細節相關的知識點。由此看來,在平常開發中還須要注重基礎。尤爲對於開發經驗是 1-5年內的 Android Developer,面試官考察的多數是基礎知識是否牢固,溝通表達能力,總結能力。雖然這次面試黃了,但不失爲一次很好的經歷。

###閱讀更多

面試官:請你介紹一下你的項目經驗

談一下Application和Context

面試官:說說你對網絡請求加密的理解?

談一談跨平臺開發

Android屏幕配適、版本配適與多語言支持

相信本身,沒有作不到的,只有想不到的

在這裏得到的不只僅是技術!

#

相關文章
相關標籤/搜索