面試主題我這裏打算分爲三部分去發佈,大家可以關注我一下,以免錯過。
Android基礎知識
1.Looper總結
- Looper通過prepare方法進行實例化,先從他的成員變量sThreadLocal中拿取,沒有的話就new 一個Looper,然後放到sThreadLocal中緩存。每個線程只能創建一個Looper實例
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
- Looper通過loop方法開啓循環隊列,裏面開啓了死循環,沒有msg時候會阻塞
- 在ActivityThread的main方法中也就是Activity啓動的時候,已經調用了Looper.prepareMainLopper()方法
2.ThreadLocal在Looper中的使用
爲了解決多個線程訪問同一個數據問題,同步鎖的思路是線程不能同時訪問一片內存區域.而ThreadLocal的思路是,乾脆給每個線程Copy一份一摸一樣的對象,線程之間各自玩自己的,互相不影響對方 常見ThreadLocal應用場景:確保在每一個線程中只有一個Looper的實例對象
- ThreadLocal的set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
- ThreadLocal的get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
簡而言之:先拿到當前線程,再從當前線程中拿到ThreadLocalMap,通過ThreadLocalMap來存儲數據。(ThreadLocalMap是Thread的成員變量)
- handler postDelay這個延遲是怎麼實現的:handler.postDelay直接將消息插入MessageQueue,以MessageQueue的時間順序排列和喚醒的方式結合實現的。
3.Service 和 IntentService
Activity對事件響應不超過5秒,BroadcastReceiver執行不超過10秒,Service耗時操作爲20秒。否則系統會報ANR
- 使用startService()方法啓用服務後,調用者與服務之間沒有關連。調用者直接退出而沒有調用stopService的話,Service會一直在後臺運行
- 使用bindService()方法啓用服務,調用者與服務綁定在一起了,調用者一旦退出,服務也就自動終止
- IntentService是Service的子類,會創建子線程來處理所有的Intent請求,其onHandleIntent()方法實現的代碼,無需處理多線程問題
4.FragmentPageAdapter和FragmentPageStateAdapter的區別
- FragmentPageAdapter在每次切換頁面的的時候,沒有完全銷燬Fragment,適用於固定的,少量的Fragment情況。默認notifyDataSetChanged()刷新是無效的
- FragmentPageStateAdapter在每次切換頁面的時候,是將Fragment進行回收,適合頁面較多的Fragment使用,這樣就不會消耗更多的內存
5.Sqlite數據庫,什麼是事務
事務是由一個或多個sql語句組成的一個整體,如果所有語句執行成功那麼修改將會全部生效,如果一條sql語句將銷量+1,下一條再+1,倘若第二條失敗,那麼銷量將撤銷第一條sql語句的+1操作,只有在該事務中所有的語句都執行成功纔會將修改加入數據庫中
sqlite數據庫相關操作,主要包括創建和增刪改查,事務
6.怎麼做Sqlite數據庫升級
- 直接刪除老數據庫,但會造成數據丟失,一般不採用。
- 對數據庫進行升級。
7.invalidate與requestLayout區別
- view調用invalidate將導致當前view的重繪,viewGroup調用invalidate會使viewGroup的子view調用draw
- requestLayout方法只會導致當前view的measure和layout,而draw不一定被執行。只有當view的位置發生改變纔會執行draw方法
8.View和ViewGroup區別
- ViewGroup的onInterceptTouchEvent默認返回false,即不攔截事件,View沒有攔截事件方法,View默認時消耗事件的
- ViewGroup默認不會調用onDraw方法,View默認會調用onDraw方法。可以通過setWillNotDraw(boolean willNotDraw)來指定是否調用onDraw方法
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
9.android版本新特性
- 5.0 引入Material Design主題
- 6.0 運行時權限
- 7.0 文件讀寫權限適配FileProvider 移除了對 Apache HTTP 客戶端的支持,建議使用 HttpURLConnection 代替。繼續使用 Apache HTTP API,必須先在 build.gradle 文件中配置: android { useLibrary 'org.apache.http.legacy' } 複製代碼
- 8.0 爲所有通知分配渠道 app內更新下載好的apk文件,需要用戶開啓未知應用安裝權限
- 9.0 使用安全的網絡訪問,如果使用http請求會報錯。Android 9.0 網絡適配
- 10.0 存儲空間分區存儲,沙盒模式
10.Android中一張圖片佔據的內存大小是如何計算
- 圖片來源是 res 內的不同資源目錄時,系統會根據設備當前的 dpi 值以及資源目錄所對應的 dpi 值,做一次分辨率轉換,規則如下:新分辨率 = 原圖橫向分辨率 * (設備的 dpi / 目錄對應的 dpi ) * 原圖縱向分辨率 * (設備的 dpi / 目錄對應的 dpi )
- 其他圖片的來源,如磁盤,文件,流等,均按照原圖的分辨率來進行計算圖片的內存大小
- 一張圖片佔用的內存大小的計算公式:分辨率 * 像素點大小;但分辨率不一定是原圖的分辨率,需要結合一些場景來討論,像素點大小就幾種情況:ARGB_8888(4B)、RGB_565(2B) 等等
11.APP啓動速度優化
- 用adb命令可以檢測啓動時間,示例如下:
adb shell am start -W [packageName]/[.MainActivity]
./adb shell am start -W "com.hchstudio.dict"/".MainActivity"
WaitTime爲我們所關注的啓動時間
- app的啓動流程,主要需要減少Application和啓動界面的onCreate方法
- 的app首頁主題樣式加上android:windowBackground,放一下app的背景圖片,這樣即使app啓動慢,也會首先加載背景,這樣就會給用戶造成一種假象,認爲是app已經啓動
<!--AppTheme.Launcher爲啓動界面的主題樣式-->
<style name="AppTheme.Launcher">
<item name="android:windowBackground">@color/colorLauncher</item>
</style>
12.內存抖動
內存抖動是由於短時間內有大量對象進出新生區導致的,它伴隨着頻繁的GC,gc會大量佔用ui線程和cpu資源,會導致app整體卡頓。
避免發生內存抖動的幾點建議:
- 儘量避免在循環體內創建對象,應該把對象創建移到循環體外。
- 注意自定義View的onDraw()方法會被頻繁調用,所以在這裏面不應該頻繁的創建對象。
- 當需要大量使用Bitmap的時候,試着把它們緩存在數組或容器中實現複用。
- 對於能夠複用的對象,同理可以使用對象池將它們緩存起來。
13.Android中ClassLoader的種類&特點:
- BootClassLoader(Java的BootStrap ClassLoader):
用於加載Android Framework層class文件。
- PathClassLoader(Java的App ClassLoader):
只能加載已經安裝過的apk的dex文件
- DexClassLoader(Java的Custom ClassLoader):
可以從一個jar包或者未安裝的apk中加載dex文件
- BaseDexClassLoader:
是PathClassLoader和DexClassLoader的父類。
14.SharePreference爲什麼不能存儲較大value
public String getString(String key, @Nullable String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
private void awaitLoadedLocked() {
while (!mLoaded) {
try {
wait();
} catch (InterruptedException unused) {
}
}
- SharePreference存值的時候,內部會有一個靜態的map保存了你所有的key和value
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
final String packageName = getPackageName();
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
15.實現View滑動的幾個辦法
- View自身提供的scrollTo()和scrollBy方法。但只適合對View內容的滑動
- 使用動畫。但滑動後的View點擊沒有效果,所以適用於沒有交互的View
- 改變佈局參數,比如layoutParams.left。比動畫稍微複雜,適合有交互的View
Scroller使用。調用startScroll方法,然後invidate() --> View會調用onDraw(),裏面會調用computeScroll(),此方法默認空實現,需要自行實現 --> 重寫computeScroll(),實現滑動,如果沒有結束,postInvalidate()重繪
16.事件分發
- 源碼
// 1. Activity的dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//交給PhoneWindow處理
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
// 2. PhoneWindow的superDispatchTouchEvent()方法
public boolean superDispatchTouchEvent(MotionEvent event) {
//交給DecorView處理
return mDecor.superDispatchTouchEvent(event);
}
// 3. 由於DecorView是FrameLayout子類,所以事件會被傳遞到DecorView的子View也就是,setContentView設置的View中
- View的事件分發
17.View的測量
18.內存泄露
產生內存泄露的原因:某個對象應該銷燬,但因爲被其他對象持有無法銷燬就會產生內存泄漏。
比如:Handler 引起的內存泄漏。Activity已經銷燬,但銷燬後handleMessage方法被調用,內部類還持有外部類Activity的引用。解決辦法:
1.將內部類聲明爲靜態(kotlin內部類默認就是靜態)
2.或者用弱引用
3.用生命週期更長的比如applicationContext代替activity
- 爲什麼內部類會持有外部類引用
因爲編譯時候,內部類構造方法中會傳入外部類的引用
//原代碼
class InnerClassOutClass{
class InnerUser {
private int age = 20;
}
}
//class代碼
class InnerClassOutClass$InnerUser {
private int age;
InnerClassOutClass$InnerUser(InnerClassOutClass var1) {
this.this$0 = var1;
this.age = 20;
}
}
19.Activity,View,Window三者關係
- 一個Activity包含了一個Window對象,這個對象是由PhoneWindow來實現的
- PhoneWindow將DecorView作爲整個應用窗口的根View
- 而DecorView又將屏幕劃分爲兩個區域:一個是TitleView,另一個是ContentView,我們平時所寫的就是展示在ContentView中的
最後
在這裏我也分享一份由幾位大佬一起收錄整理的 Flutter進階資料以及Android學習PDF+架構視頻+面試文檔+源碼筆記 ,並且還有 高級架構技術進階腦圖、Android開發面試專題資料,高級進階架構資料……
這些都是我閒暇時還會反覆翻閱的精品資料。可以有效的幫助大家掌握知識、理解原理。當然你也可以拿去查漏補缺,提升自身的競爭力。
如果你有需要的話,可以前往 GitHub 自行查閱。