有人說這是2021字節跳動-初級Android工程師的面經?嚇到我了!我還是去搬磚吧! 其他 最後

我的情況

我在大三的時候,沒有參加春招,也沒有參加秋招,我大三 三月份的時候在實習僧上投了幾個簡歷,發現面的都很水,原來我在大學的時候,成績還算ok,編程能力在班裏也是前幾的,但是說實話,找工作經驗真的不足吧,我編程的時候,沒關注過JVM原理,也沒關注過Java裏面一些實現機制,一些【源碼】也沒看過,這個時候,我就知道還需要努力了。 我找了個幾個人的小公司,先幹着了,邊幹邊複習吧,補一補基礎,後來,跳槽到了58企服,一個a輪的創業公司,乾的也很happy,然後就放棄秋招了,然後今年三月份,公司取消了移動端開發,我就又失業了。。。

我在三月初開始複習,找工作了,前後一共大概一個半月的時間吧,最後收到了字節跳動的offer,打算過去了。

以一個過來人的身份,給大家一點建議吧:

  • 畢業第一份工作,儘可能去大公司

    理由: 大公司穩定性較高,不用擔心失業,工資較高,生活水平不會差,大公司技術棧沉澱了很久了,比一般公司會好很多,大公司門檻相對較高,進來的人 基礎不會太差,大家合作比較愉悅,大公司 一般來說 自帶用戶量,隨便搞點什麼,日活百萬 千萬,會對技術架構有一定要求,能學到的比較多。聲明: 因爲我在幾個人的公司待過,也在100多人的公司待過,我深知創業公司的苦,但是我不是引戰,我只是說出我的想法,我不會和別人討論,畢業去大公司好,還是創業公司好,也希望評論區不要有人和我討論這個問題,我只說出我的想法。

    • 不要急於實習,春招儘量參加完

    理由: 我覺得春招很長,機會很多,一步一步腳踏實地的走吧,舉一個例子,沒有offer不要着急去小公司實習,如果最後還沒有offer可以考慮。如果收到一個還ok的offer,儘可能追求一下更好的offer,不要有一個offer就滿足,多嘗試,都付出這麼多了,不多面面也可惜,不過面面,也不知道自己有多強,也不知道自己還有哪裏不足。

    • 校招一定要參加

    理由: 我認爲即使春招定了,說我能留在字節,能留在阿里,我覺得也要儘可能走一下校招,因爲校招會提供更多的機會,會讓我們 以後不後悔,沒準能拿到更好的sp或者更好的部門呢,一般 你有其它公司的sp,本公司也願意給你提sp的。我覺得秋招不要懶,多複習多嘗試,其次就是如果工作很忙很忙,建議請個小假,認真嘗試校招,沒準能收穫更好的機會。

    • 在校期間怎麼學習

    我覺得,無論什麼專業吧,都應該把劍指offer和leetcode一些題多刷一刷,記住無論以後想幹什麼,我覺得這是基礎。然後就是認真複習本專業的知識了,我拿安卓舉例,那就是jvm基礎,java基礎,安卓基礎,安卓進階,算法,操作系統,計算機網絡等。我覺得這些都要看,都要認真看。我覺得 自己 制定計劃吧,這些點 都要複習到,不要有幻想說,能不能我這裏不行,他考不到什麼的,多複習,一定要多複習。

    還有很重要的一點,要說一下,我也很鼓勵在校期間,什麼都可以接觸,什麼都可以學習,廣度很重要,但是在找第一份工作的時候,有用的基本只有深度,所以一定要有一個點是深入的,不能完全發散,什麼都不深入,這樣很致命。找一個深入的點,先深入下去,這個很重要,不要以爲什麼都會一些也可以。

    • 心態

    我覺得求職過程中吧,肯定是心態最重要,半個月沒筆試,半個月沒面試,我覺得這可能是常態,我覺得心態不要蹦,不要看到別人有offer自己沒offer就不行了。我覺得不能光看見別人的offer吧,也要看到別人的努力,再就是面試本身就具有很大的隨機性,所以不要盲目妄自菲薄,也不要沒有自信,慢慢來,一步一步來,跟自己比唄,看看有沒有進步。堅持就好,慢慢來,肯定能成功,就腳踏實地的搞,重積累,每天都要有收穫。

    • 如何投簡歷

    首先我們要有一份還ok的簡歷吧,這個就找學長或者朋友,老師幫忙看看吧,這個都ok,投簡歷的渠道吧,就是牛客網上的內推,學長的內推,朋友的內推,如果這些都試了還是沒什麼offer,還可以試試實習僧,脈脈,boss直聘上面,上面還是有一些大公司的散招的,這些也都可以多試試,多溝通,爭取收穫到面試的機會。找人內推的時候,儘可能簡短的描述清晰,自己的優勢的點,這樣能增加面試的機率。

    • 加分項

    名校 大公司實習經驗 大賽獲獎經驗 github 博客 說明一下,前面的點,博主都沒有,不過github 和博客還ok


知識合集


注意,由於文章篇幅問題,關鍵內容都只展示部分,具體內容看電子實物
有需要的朋友直接點擊此處的藍色字體獲取完整文檔。

點擊【GitHub】免費分享大牛個人面試進階學習筆記!

Java基礎

什麼是樂觀鎖?

樂觀鎖:假設每次去拿數據都認爲別人不會修改,所以不會上鎖。但是在更新的時候會判斷一下此期間別人有沒有去更新這個數據。一般用在讀比較多,寫比較少的情況。

悲觀鎖:假設每次都是最壞情況,每次去拿數據時別人都會修改,所以每次拿數據的時候都會上鎖,這樣別人想拿這個數據就會被阻塞直到它拿到鎖,多寫少讀時使用。

volatile關鍵字

  1. 保證可見性,不保證原子性
  2. 禁止指令重排序
  3. 不緩存,每次都是從主存中取

hashmap 原理,紅黑樹是什麼?

  • 數組+鏈表,鏈表過長時,會導致查詢效率退化
  • 數組+鏈表+紅黑樹,當鏈表長度大於8轉爲紅黑樹
  • HashMap 的默認初始大小爲 16,初始化大小必須爲 2 的冪,最大大小爲 2 的 30 次方。數組中存儲的鏈表節點 Entry 類實現於 Map.Entry 接口,它實現了對節點的通用操作。HashMap 的閾值默認爲 “容量 * 0.75f”,當存儲節點數量超過該值,則對 map 進行擴容處理。
  • 線程不安全的容器,解決併發問題使用ConcurrentHashMap(高效)或者是Collections.synchronizedMap()。Collections.synchronizedMap()其實就是每個方法加一個synchronize,其實和HashTable 差不多。

紅黑樹

  • 平衡的二叉查找樹
  • 節點是紅色或者是黑色
  • 根節點是黑色
  • 每個葉子的節點都是黑色的空節點(NULL)
  • 每個紅色節點的兩個子節點都是黑色的
  • 從任意節點到其每個葉子的所有路徑都包含相同的黑色節點
  • 插入時會涉及到變色和旋轉

jvm內存分配

  • 程序計數器
  • Java虛擬機棧
  • 本地方法棧
  • Java堆
  • 方法區
  • 運行時常量池
  • 直接內存

String,StringBuffer,StringBuilder 區別

String,StringBuffer,StringBuilder最終底層存儲與操作的都是char數組。但是String裏面的char數組是final的,而StringBuffer,StringBuilder不是,也就是說,String是不可變的,想要新的字符串只能重新生成String。而StringBuffer和StringBuilder只需要修改底層的char數組就行。相對來說,開銷要小很多。

String的大多數方法都是重新new一個新String對象返回,頻繁重新生成容易生成很多垃圾。StringBuffer是線程安全的,StringBuilder是線程不安全的,因爲StringBuffer的方法是加了synchronized鎖起來了的,而StringBuilder沒有。增刪比較多時用StringBuffer或StringBuilder(注意單線程與多線程)。實際情況按需而取吧,既然已經知道了裏面的原理。

安卓基礎

安卓各版本大變化(Android 6.0到10.0),兼容適配

Android 5.0

  • Material Design
  • ART虛擬機

Android 6.0

  • 應用權限管理
  • 官方指紋支持
  • Doze電量管理
  • 運行時權限機制->需要動態申請權限

Android 7.0

  • 多窗口模式
  • 支持Java 8語言平臺
  • 需要使用FileProvider訪問照片
  • 安裝apk需要兼容

Android 8.0

  • 通知
  • 畫中畫
  • 自動填充
  • 後臺限制
  • 自適應桌面圖標->適配
  • 隱式廣播限制
  • 開啓後臺Service限制

Android 9.0

  • 利用 Wi-Fi RTT 進行室內定位
  • 劉海屏 API 支持
  • 多攝像頭支持和攝像頭更新
  • 不允許調用hide api
  • 限制明文流量的網絡請求 http

Android 10

  • 暗黑模式
  • 隱私增強(後臺能否訪問定位)
  • 限制程序訪問剪貼板
  • 應用黑盒
  • 權限細分需兼容
  • 後臺定位單獨權限需兼容
  • 設備唯一標示符需兼容
  • 後臺打開Activity 需兼容
  • 非 SDK 接口限制 需兼容

熱修復原理

原理

  1. 安卓在加載class時會通過雙親委託機制去加載一個類,先讓父類去加載,如果找不到再讓子類去加載某個類。
  2. 通過查看ClassLoader源碼發現findClass方法是由每個子類自己實現的,比如BootClassLoader或者BaseDexClassLoader。而PathClassLoader是繼承自BaseDexClassLoader的,它的findClass也是在BaseDexClassLoader裏面實現的。
  3. BaseDexClassLoader的findClass裏面使用了另一個對象DexPathList去查找對應的class,這是安卓裏面特有的實現。在DexPathList對象裏面有一個屬性dexElements,dexElements是用於存放加載好了的dex數組的,查找class是從這個dexElements數組裏面去找的。
  4. dexElements裏面存放的是Element對象,findClass最終會交給Element去實現,Element又會交給Element裏面的一個屬性DexFile去實現。我看了下,最終是用native實現的。
  5. 回到上面的第3步中的DexPathList對象從dexElements數組裏面查找class,從數組的前面往後找,找到了就返回結果,不再繼續查找。
  6. 所以當我們把修復好bug了的class,搞成dex,然後通過反射等技術放到dexElements的最前面,這樣系統在通過PathClassLoader找到class時,就能先找到我們放置的修復好bug的class,然後就不會再往後找了,相當於實現了熱修復。這樣有bug的class就不會被用了。應了一句古話,近水樓臺先得月。
  7. 第6點中的反射,流程是:獲取到PathClassLoader,然後反射獲取到父類中的DexPathList對象,然後再反射到DexPathList對象中的dexElements數組。然後將補丁(dex)轉爲Element對象,插入到dexElements數組的前面(先複製出來,再合併,再通過反射放回去)。

一句話總結。將修復好的類放在dexElements的最前面,這樣在加載類的時候就會被優先加載到而達到修復的目的。

MVC,MVP,MVVM

首先需要知道的是爲什麼要進行技術框架的設計?肯定是爲了低耦合,提高開發效率是吧。所以不要爲了設計而設計。

MVC

在Android中View和Controller一般就是被Activity充當了,當邏輯非常多,操作非常複雜時,Activity代碼量非常龐大,不易維護。

  • Model : 模型層,業務邏輯+數據存儲等
  • View : 用戶界面,一般就是xml+Activity
  • Controller : 控制層,一般就是Activity

MVP

我個人角度,現在(2019年10月29日20:02:49)大多是使用這種方式,既不復雜也解耦合了。

  • Model:模型層,業務邏輯+數據存儲+網絡請求
  • View:視圖層,View繪製和用戶交互等,一般是Activity
  • Presenter:呈現層,連接V層和M層,完成他們之間的交互

MVVM

爲了更加分離M,V層,所以有了MVVM。

  • Model:模型層,業務邏輯+數據存儲+網絡請求
  • View:視圖層,View繪製和用戶交互等,一般是Activity
  • ViewModel:其實就是Presenter和View的數據模型的合體。雙向綁定,View的變動會反應到ViewModel中,數據的變動也會反應到View上。

組件化的好處

  1. 任意修改都需要編譯整個工程,效率低下。
  2. 解耦,有利於多人團隊協作開發
  3. 功能複用

app啓動流程

  1. Launcher startActivity
  2. AMS startActivity
  3. Zygote fork進程
  4. Activity main()
  5. ActivityThread 進程loop循環
  6. 開啓Activity,開始生命週期回調…

Activity啓動流程

  1. Activity startActivityForResult
  2. Instrumentation execStartActivity
  3. AMS startActivity
  4. ApplicationThread scheduleLaunchActivity
  5. ActivityThread.H handleMessage -> performLaunchActivity
  6. Activity attach
  7. Instrumentation callActivityOnCreate

app體積優化

可以使用lint工具,檢測出沒有用的文件。同時可以開啓資源壓縮,自動刪除無用的資源。儘量多使用可繪製對象,某些圖像不需要靜態圖像資源,框架可以在運行時動態繪製圖像。儘量自己寫Drawable,能不用UI切圖就不用,佔用空間小。

重用資源,比如一個三角按鈕,點擊前三角朝上代表收起的意思,點擊後三角朝下,代表展開,一般情況下,我們會用兩張圖來切換,我們其實完全可以用旋轉的形式去改變。比如同一圖像的着色不同,我們可以用android:tint和tintMode屬性,低版本可以使用ColorFilter。

壓縮PNG和JPEG文件,可以減少PNG文件的大小,而不會丟失圖像質量。使用WebP文件格式,可以使用WebP文件格式,而不是使用PNG或JPEG文件。可以使用AS將現有的BMP、JPG、PNG或靜態GIF圖像轉換成WebP格式。使用矢量圖形.svg;代碼混淆,使用proGuard代碼混淆器工具,它包括壓縮,優化,混淆等功能。這個大家太熟悉。插件化,將功能模塊放服務器上,按需下載,可以減少安裝包大小。

app啓動優化

利用提前展示出來的Window,快速展示出來一個節目,給用戶快速反饋的體驗。障眼法,治標不治本。

避免在啓動時做密集沉重的初始化(Heavy app initialization)。某些SDK初始化放在異步去加載(比如友盟,bugly這樣的業務非必要可以異步加載),比如地圖,推送等,非第一時間需要的可以在主線程做延時啓動(比如閃屏頁),當程序已經啓動起來之後,再進行初始化。對於網絡,圖片請求框架就必須在主線程中初始化了。啓動時,避免I/O操作,反序列化,網絡操作,佈局嵌套等耗時操作。

app佈局優化

  • 如果父控件有顏色,也是自己需要的顏色,那麼就不必在子控件加背景顏色
  • 如果子控件有背景顏色,並且能完全覆蓋父控件,那麼父控件不用設置背景顏色
  • 儘量減少不必要的嵌套
  • 能用LinearLayout和FrameLayout,就不要用RelativeLayout,因爲RelativeLayout相對比較複雜,測繪也相對耗時。
  • include和merge一起使用,增加複用,減少層級
  • ViewStub按需加載,更加輕便
  • 複雜界面選擇ConstraintLayout,可有效減少層級

app內存優化

頻繁使用字符串拼接用StringBuilder或者StringBuffer;ArrayMap、SparseArray替換HashMap;避免內存泄漏。

  • 集合類泄漏(集合一直引用着被添加進來的元素對象)
  • 單例/靜態變量造成的內存泄漏(生命週期長的持有了生命週期短的引用)
  • 匿名內部類/非靜態內部類
  • 資源未關閉造成的內存泄漏
  • 檢測內存泄漏的幾個工具: LeakCanary,TraceView,Systrace,Android Lint和Memory Monitor+mat

內存泄漏有哪些

  • 集合類泄漏(集合一直引用着被添加進來的元素對象)
  • 單例/靜態變量造成的內存泄漏(生命週期長的持有了生命週期短的引用)
  • 匿名內部類/非靜態內部類
  • 資源未關閉造成的內存泄漏
  • 網絡,文件等流忘記關閉
  • 手動註冊廣播時,退出時忘記unregisterReceiver()
  • Service執行完成後忘記stopSelf()
  • EventBus等觀察者模式的框架忘記手動解除註冊

app線程優化

線程池避免存在大量的Thread,重用線程池內部的線程,從而避免了線程的創建和銷燬帶來的性能開銷,同時能有效控制線程池的最大併發數,避免大量線程因互相搶佔系統資源而導致阻塞線現象發生。

分類

  • FixedThreadPool 數量固定的線程池
  • CachedThreadPool 只有非核心線程,數量不定,空閒線程有超時機制,比較適合執行大量耗時較少的任務
  • ScheduledThreadPool 核心線程數量固定,非核心線程沒有限制。主要用於執行定時任務和具有固定中週期的重複任務。
  • SingleThreadPool 只有一個核心線程,確保所有的任務在同一個線程順序執行,統一外界任務到一個線程中,這使得在這些任務之間不需要處理線程同步的問題。

優點

  • 減少在創建和銷燬線程上所花的時間以及系統資源的開銷
  • 不使用線程池有可能造成系統創建大量的線程而導致消耗完系統內存以及”過度切換”

注意點

  • 如果線程池中的數量未達到核心線程的數量,則直接啓動一個核心線程來執行任務
  • 如果線程池中的數量已經達到或超過核心線程的數量,則任何會被插入到任務隊列中等待執行
  • 如果2中的任務無法插入到任務隊列中,由於任務隊列已滿,這時候如果線程數量未達到線程池規定的最大值,則會啓動一個非核心線程來執行任務
  • 如果3中的線程數量已經達到線程池最大值,則會拒絕執行此任務,ThreadPoolExecutor會調用RejectedExecutionHandler的rejectedExecution()方法通知調用者

Android換膚如何實現,原理

重新設置LayoutInflater的Factory2,從而攔截創建View的過程,然後搞成自己的控件,想怎麼換膚就怎麼換膚。

fresco原理,glide原理,兩者區別,哪個更省內存

這塊暫時不懂,加入todo。

Handler原理,Android 消息機制

之前寫過一篇文章,死磕Android_Handler機制你需要知道的一切,裏面介紹得很詳細,順便說了一下爲什麼loop不會阻塞主線程問題。

Handler機制的關鍵在於對於ThreadLocal原理的理解,線程私有數據。利用ThreadLocal機制將Looper存放到線程內部,perfect !

Android 系統架構

應用層,應用框架層,系統運行庫層,硬件抽象層和Linux內核層。

常用佈局有哪些

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • ConstraintLayout
  • CoordinatorLayout

Android數據存儲有幾種方式

  • SharedPreferences: 小東西,最終是xml文件中,key-value的形式存儲的.
  • 文件
  • 數據庫
  • ContentProvider
  • 網絡

View,SurfaceView

  • View是Android中所有控件的基類
  • View適用於主動更新的情況,而SurfaceView則適用於被動更新的情況,比如頻繁刷新界面。
  • View在主線程中對頁面進行刷新,而SurfaceView則開啓一個子線程來對頁面進行刷新。
  • View在繪圖時沒有實現雙緩衝機制,SurfaceView在底層機制中就實現了雙緩衝機制。

jni調用流程

我之前也寫過簡單的demo,JNI Java與C的相互調用與基本操作。

組件之間相互引用,如何解決

  • 調用其他組件的對外提供的方法:之前看到過一種思路,利用”接口+實現”的方式,定義一個ComponentBase 中間層,然後裏面有每個組件對外提供方法調用的Interface,每個組件在初始化的時候就把這些Interface給實現了,然後其他組件需要用的時候就從ComponentBase裏面取。
  • 界面跳轉:ARouter

Android 數字簽名

校驗用戶身份,校驗數據的完整性。

fragment用在哪裏,與Activity的區別

  • 當Activity需要模塊化的時候
  • 不同設備上的適配,比如平臺和手機
  • Activity相對Fragment而言,非常笨重,一般小界面小模塊用Fragment比較合適,或者首頁的tab之類的。

View繪製原理

Retrofit和OkHttp原理,攔截器

  • Retrofit的話,源碼寫的非常非常棒。主要是通過動態代理+獲取方法上面的註解等,然後組裝請求網絡的參數,最後用OkHttp去請求網絡。
  • OkHttp的攔截器鏈設計得非常巧妙,是典型的責任鏈模式。並最終由最後一個鏈處理了網絡請求,並拿到結果。

點擊事件傳遞機制,事件分爲哪幾種

事件傳遞大體過程:Activity–> Window–>DecorView –> View樹從上往下,傳遞過程中誰想攔截就攔截自己處理。MotionEvent是Android中的點擊事件。主要事件類型:

  • ACTION_DOWN 手機初次觸摸到屏幕事件
  • ACTION_MOVE 手機在屏幕上滑動時觸發,會回調多次
  • ACTION_UP 手指離開屏幕時觸發

需要關注的幾個方法。

  • dispatchTouchEvent(event);
  • onInterceptTouchEvent(event);
  • onTouchEvent(event);

上面3個方法可以用以下僞代碼來表示其關係:

public boolean dispatchTouchEvent(MotionEvent ev) {
 boolean consume = false;//事件是否被消費
 if (onInterceptTouchEvent(ev)) {//調用onInterceptTouchEvent判斷是否攔截事件
 consume = onTouchEvent(ev);//如果攔截則調用自身的onTouchEvent方法
 } else {
 consume = child.dispatchTouchEvent(ev);//不攔截調用子View的dispatchTouchEvent方法
 }
 return consume;//返回值表示事件是否被消費,true事件終止,false調用父View的onTouchEvent方法
}

anr如何產生,Service觸發anr是多長時間(20秒),如何解決anr?如何解決那種莫名其妙的anr?

我覺得anr就是在主線程做了耗時操作,比如io、讀寫文件、數據庫操作等等。anr發生之後一般會有日誌,在/data/anr/traces.txt裏面。可以參考我的這篇文章拿anr日誌,Android 未root查看ANR異常:

Dialog和Activity是同一個Window?

不是同一個。

  • Activity的attach方法,這裏是爲Activity實例化了一個PhoneWindow實例
  • Dialog的構造方法裏面也是實例化了一個PhoneWindow實例

Window,Activity,Dectorview之間的關係

ConstraintLayout和RelativeLayout在繪製方面有何差別?

todo。

onClick事件和onTouchListener在哪裏回調?

如果一個View需要處理事件,它設置了OnTouchListener,那麼OnTouchListener的onTouch方法會被回調。如果onTouch返回false,則onTouchEvent會被調用,反之不會。在onTouchEvent方法中,事件爲Action.UP的時候會回調OnClickListener的onClick方法,可見OnClickListener的優先級很低。

LinearLayout是如何測量(measure)的?如果有weight又是如何測量的?

先做一次測量,做完之後有空間剩餘,有weight的View再測量一下,分一下剩餘的空間。

屏幕適配

先前有鴻洋的AndroidAutoLayout,根據寬高進行控件縮放,非常經典,很多項目可能都還在使用,但是已經停止更新了。然後就是有名的今日頭條方案,出來還是有點時間了。原理其實就是更改density。

屏幕的寬度=設計稿寬度 * density。

然後有AndroidAutoSize庫,將今日頭條方案融合進去還完善了很多問題,易用,完美。

其他

Java四種引用

  • 強引用,默認就是,寧願OOM,也不回收
  • 弱引用,內存不夠會被回收
  • 軟引用,GC時會被回收
  • 虛引用,它的作用在於跟蹤垃圾回收過程,在對象被收集器回收時收到一個系統通知。

項目中遇到的最困難的事情是什麼?如何解決的?

每個人遇到的情況不同,這個提前思考一下自己做過的項目最有挑戰的地方。

Kotlin優勢

  • 完全兼容java
  • 空安全
  • 支持lambda表達式
  • 支持擴展函數
  • 更少的代碼量,更快的開發速度

缺點就是有時候代碼閱讀性可能會降低。

Kotlin 協程是什麼?

就是一個線程框架,提供了一套操作線程的api。

二叉樹,廣度優先遍歷,深度優先遍歷

推薦小灰的漫畫算法。還有其他的一些,隨便聊聊:

  • tcp,http,https,socket
  • 敏捷開發
  • 你經常使用哪些設計模式,常見設計模式的運用
  • 3年之後工資怎麼想的
  • 你的優勢
  • 職業規劃(3年後幹啥,5年後幹啥)
  • 應用,進程,線程之間的區別

最後

有些東西你不僅要懂,而且要能夠很好地表達出來,能夠讓面試官認可你的理解,例如Handler機制,這個是面試必問之題。有些晦澀的點,或許它只活在面試當中,實際工作當中你壓根不會用到它,但是你要知道它是什麼東西。

對於程序員來說,要學習的知識內容、技術有太多太多,要想不被環境淘汰就只有不斷提升自己,從來都是我們去適應環境,而不是環境來適應我們!

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