Interview - Android

# Task, Application, Process

https://blog.csdn.net/yyyysjhappy/article/details/20127615

Application:應用程序,是一組組件的集合,當我們寫完了多個組件,並且在manifest文件中註冊了這些組件之後,把這些組件和組件使用到的資源打包成apk,我們就可以說完成了一個application。因此,Application是由四大組件組成的。

Task:Task是在程序運行時,只針對Activity的概念,task是一組相互關聯的activity的集合。它是存在於framework層的一個概念,控制界面的跳轉和返回。這個task存在於一個稱爲back stack的數據結構中,也就是說,framework是以棧的形式管理用戶開啓的activity。這個棧的基本行爲是,當用戶在多個activity之間跳轉時,執行壓棧操作,當用戶按返回鍵時,執行出棧操作。

Process:進程是操作系統內核中的一個概念,表示只接受內核調度的執行單位。在應用程序的角度看,我們用java編寫的應用程序,運行在dalvik虛擬機中,可以認爲一個運行中的dalvik虛擬機實例佔有一個進程,所以,在默認情況下,一個應用程序的所有組件運行在同一個進程中。但是這種情況也有例外,即應用程序中的不同組件可以運行在不同的進程中,只需要在manifest中用process屬性指定組件所運行的進程的名字。


# Activity的四種啓動方式

https://blog.csdn.net/yyyysjhappy/article/details/20127615

Standard:標準啓動模式,也是activity的默認啓動模式。在這種模式下啓動的activity可以被多次實例化,即在同一個任務中可以存在多個activity的實例,每個實例都會處理一個Intent對象。如果Activity A的啓動模式爲standard,並且A已經啓動,在A中再次啓動Activity A.即調用startActivity(new Intent(this,A.class)),會在A的上面再次啓動一個A的實例,即當前的棧中狀態爲A->A。

Single Top:如果一個以singleTop模式啓動的activity的實例已經存在於任務棧的棧頂,那麼再啓動這個Activity時,不會創建新的實例,而是重用位於棧頂的那個實例,並且會調用該實例的onNewIntent()方法將Intent對象傳遞到這個實例中。舉例來說,如果A的啓動模式爲SingleTop,並且A的一個實例已經存在於棧頂中,那麼再調用startActivity(new Intent(this,A.class));啓動A時,不會創建A的實例,而是重用原來的實例,並且調用原來實例的onNewIntent()方法。這時任務棧中還是隻有A的一個實例。

Single Task:雖然官方文檔說:如果一個activity的啓動模式爲singleTask,那麼系統總會在一個新任務的最底部(root)啓動這個activity並且被這個activity啓動的其他activity會和該activity同時存在於這個新任務棧中,誒過系統中已經存在這樣的一個activity則會重用這個實例,並且調用它的onNewIntent()方法。即這樣的一個activity在系統中只會存在一個實例。但是官方文檔中的這種說法是不正確的。(只有在singleTask模式的啓動中添加屬性TaskAffinity纔會在新的任務棧中啓動singleTask模式的activity;在啓動一個singleTask的Activity實例時,如果系統中已經存在這樣的一個實例,就會將這個實例調到任務棧的棧頂,並清除它當前任務棧中位於它上面的所有的Activity)

Single Instance:總是在新的任務中開啓,並且這個新的任務中有且只有這一個實例,也就是說被該實例啓動的其他activity會自動運行於另一個任務中。當再次啓動該activity的實例看,會重用已存在的任務和實例。並且會調用這個實例的onNewIntent()方法,將Intent實例傳遞到該實例中。和singleTask相同,同一時刻在系統中只會存在一個這樣的activity實例;以singleInstance模式啓動的Activity具有獨佔性,即它會獨自佔用一個任務,被他開啓的任何activity都會運行在其他任務中(官方文檔上的描述爲,singleInstance模式的Activity不允 許其他Activity和它共存在一個任務中);被singleInstance模式的Activity開啓的其他activity,能夠開啓一個新任務,但不一定開啓新的任務,也可能在已有的一個任務中開啓(當前系統中是不是已經有了一個activity B的taskAffinity屬性指定的任務)。


# 優雅退出APP

https://github.com/JackChan1999/Android-Interview/blob/master/Activity/App優雅退出.md

方法1:設置一個全局的單例容器,把所有的Activity存儲起來,退出時循環遍歷finish所有Activity,然後調用Process.killProcess(Process.myPid()) -> System.exit(0)

方法2:在BaseActivity中註冊一個退出廣播,當接收到該廣播時finish,並且在onDestroy時反註冊該廣播

方法3:設置MainActivity的加載方式爲singleTask,每次在MainActivity中雙擊back鍵時finish該Activity。(利用了singleTask在任務棧棧底的原理)

方法4:設置MainActivity的啓動方式爲singleTask,重寫MainActivity的onNewIntent方法,當傳入 “exit” 相關值時,finish該Activity。


# Activity啓動時最少有幾個線程


# onSaveInstanceState,onRestoreInstanceState

https://blog.csdn.net/fenggering/article/details/53907654

onSaveInstanceState:在onStop之前可能會被調用(用戶主動finish時不會被調用)
onRestoreInstanceState:只有在Activity確實被系統回收,重新創建Activity時纔會被調用

按下HOME鍵:onPause -> onSaveInstanceState -> onStop
回到桌面:onRestart -> onStart -> onResume (由於沒有被回收,所以沒有 onRestoreInstanceState)

切換橫豎屏:onPause -> onSaveInstanceState -> onStop -> onDestroy -> onCreate -> onStart -> onRestoreInstanceState -> onResume

onCreate和onRestoreInstanceState中Bundle的區別:onCreate中的bundle可能是空的,onRestoreInstanceState中的bundle一定非空,因爲曾經被回收過。另外兩者生命週期位置不同,可以在onStart時做一些工作。


# Service啓動Activity出現異常分析

https://github.com/JackChan1999/Android-Interview/blob/master/Activity/爲什麼service裏面startActivity拋異常.md

異常:context requires the FLAG_ACTIVITY_NEW_TASK_FLAG

Service startActivity:直接調用ContextImpl中的startActivity,這種啓動方式沒有Activity棧,因此不能以standard方式啓動,必須加上FLAG_ACTIVITY_NEW_TASK_FLAG。在ContextImpl中檢查了該flag,沒有則拋出了異常。

Activity startActivity:由於有Activity Stack,因此在Activity繼承的父類中直接調用了ActivityManagerNative.startActivity方法,沒有作該flag的檢查。


# Activity啓動生命週期

Activity生命週期


# Service啓動生命週期

Service生命週期
1). 被啓動的服務的生命週期:如果一個Service被某個Activity 調用 Context.startService 方法啓動,那麼不管是否有Activity使用bindService綁定或unbindService解除綁定到該Service,該Service都在後臺運行。如果一個Service被startService 方法多次啓動,那麼onCreate方法只會調用一次,onStart將會被調用多次(對應調用startService的次數),並且系統只會創建Service的一個實例(因此你應該知道只需要一次stopService調用)。該Service將會一直在後臺運行,而不管對應程序的Activity是否在運行,直到被調用stopService,或自身的stopSelf方法。當然如果系統資源不足,android系統也可能結束服務。

2). 被綁定的服務的生命週期:如果一個Service被某個Activity 調用 Context.bindService 方法綁定啓動,不管調用 bindService 調用幾次,onCreate方法都只會調用一次,同時onStart方法始終不會被調用。當連接建立之後,Service將會一直運行,除非調用Context.unbindService 斷開連接或者之前調用bindService 的 Context 不存在了(如Activity被finish的時候),系統將會自動停止Service,對應onDestroy將被調用。

3). 被啓動又被綁定的服務的生命週期:如果一個Service又被啓動又被綁定,則該Service將會一直在後臺運行。並且不管如何調用,onCreate始終只會調用一次,對應startService調用多少次,Service的onStart便會調用多少次。調用unbindService將不會停止Service,而必須調用 stopService 或 Service的 stopSelf 來停止服務。

4). 當服務被停止時清除服務:當一個Service被終止(1、調用stopService;2、調用stopSelf;3、不再有綁定的連接(沒有被啓動))時,onDestroy方法將會被調用,在這裏你應當做一些清除工作,如停止在Service中創建並運行的線程。


# Fragment生命週期
在這裏插入圖片描述
1). onAttached() —— 當fragment被加入到activity時調用(在這個方法中可以獲得所在的activity)。

2). onCreateView() —— 當activity要得到fragment的layout時,調用此方法,fragment在其中創建自己的layout(界面)。

3). 一旦activity進入resumed狀態(也就是running狀態),你就可以自由地添加和刪除fragment了。因此,只有當activity在resumed狀態時,fragment的生命週期才能獨立的運轉,其它時候是依賴於activity的生命週期變化的。


# onPause 和 onStop 的區別,什麼時候存儲數據

onPause 用於由一個Activity轉到另一個Activity、設備進入休眠狀態(屏幕鎖住了)、或者有dialog彈出時
onStop 用於不可見的Activity(有對話框彈出時,這時底下的activity仍然可見,所以此時onStop不會被調用)

1.從FirstActivity跳到SecondActivity時
FirstAcvity —> onCreate
FirstAcvity —>onStart
FirstAcvity —>onResume
FirstAcvity —>onPause
SecondActivity—>onCreate
SecondActivity—>onStart
SecondActivity—>onResume
FirstAcvity —>onStop

現在給AndroidMainfest.xml中的SeconedActivity屬性加入android:theme="@android:style/Theme.Dialog",則SeconedActivity將以對話框形式出現,不會對FirstAcvity形成遮蓋. 這時的狀態輸出爲:
FirstAcvity —> onCreate
FirstAcvity —>onStart
FirstAcvity —>onResume
FirstAcvity —>onPause
SecondActivity—>onCreate
SecondActivity—>onStart
SecondActivity—>onResume

這時FirstAcvity比完全遮蓋時少調用了onStop方法.

以下兩種情況下 都只會觸發onPause而不會觸發onStop
1.一個透明的包含Dialog的Activity 出現
2.按poweroff鎖屏

顯示一個非activity的Dialog,是不會調用onPause和onStop的,因爲此Dialog屬於activity


# Activity 和 Fragment如何傳遞數據

1). 啓動Fragment時,通過Fragment.setArgument(Bundle bundle)設置啓動Fragement時的參數

2). 在Activity的onAttachFragment和Fragment的onAttach方法中註冊回調接口

3). 傳遞對方的Handler,採用Post消息的方式

4). 廣播等方式


# Activity 啓動流程

在這裏插入圖片描述

https://www.jianshu.com/p/13b07beacb1f


# Android 預加載

反射使用HandlerThread的SyncBarrier,使得數據加載和Activity的加載同時進行

https://blog.csdn.net/cdecde111/article/details/54670136


# 單線程模型中Message,Handler,MessageQueue,Looper之間的關係

拿主線程來說,主線程啓動時會調用Looper.prepare()方法,會初始化一個Looper,放入Threadlocal中,接着調用Looper.loop()不斷遍歷MessageQueue,Handler的創建依賴與當前線程中的Looper,如果當前線程沒有Looper則必須調用Looper.prepare()。Handler , sendMessage到MessageQueue,Looper不斷從MessageQueue中取出消息,回調handleMessage方法。


# Looper / MessageQueue 的底層實現

休眠流程:

MessageQueue.next() -> (nextPollTimeoutMillis) nativePollOnce() ->android_os_MessageQueue_nativePollOnce() -> NativeMessageQueue.pollOnce() -> Looper.pollOnce(timeoutMillis) -> Looper.pollInner(timeoutMillis) -> epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis)

enqueue觸發喚醒流程:

MessageQueue.enqueueMessage() -> nativeWake() -> android_os_MessageQueue_nativeWake() -> NativeMessageQueue.wake() -> Looper.wake() -> TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));


# 如果有個100M大的文件,需要上傳至服務器中,而服務器form表單最大隻能上傳2M,可以用什麼方法

這個問題不是很明確我覺得,首先來說使用http協議上傳數據,特別在android下,跟form沒什麼關係。傳統的在web中,在form中寫文件上傳,其實瀏覽器所做的就是將我們的數據進行解析組裝拼成字符串,以流的方式發送到服務器,且上傳文件用的都是POST方式,POST方式對大小沒什麼限制。
回到題目,可以說假設每次真的只能上傳2M,那麼可能我們只能把文件截斷,然後分別上傳了。


# AsyncTask使用在哪些場景?它的缺陷是什麼?如何解決?

AsyncTask 運用的場景就是我們需要進行一些耗時的操作,耗時操作完成後更新主線程,或者在操作過程中對主線程的UI進行更新
缺點:
1). 非靜態的內部類或匿名內部類會導致內存泄漏
2). execute執行使用的是串行方式,後臺只有一個線程執行;executeOnExecutor(Executor)纔會採用並行執行的方式。
3). execute非Runnable只能執行一次(FutureTask只能執行一次,執行完成後會清除Callable),execute Runnable不會回調onPre,doingBackground 和 onPostExecute


# AsyncTask 在非UI線程下的使用

AsyncTask 可以在非UI線程中創建和執行,但需要注意:

假設:A (UI線程),B(子線程),C(AsyncTask中創建的線程)。在B線程中創建AsyncTask

則:onPreExecute(B線程),doingBackground(C線程),onPostExecute(A線程)

因此:onPostExecute一定在UI線程中,原理:AsyncTask中拿到了Main looper並創建了相關的InternalHandler,在執行完成後向InternalHandler投遞了消息並回調onPostExecute。

提供了傳遞Looper和Handler的方法,需要自己實現相關消息處理API,並且這些方法是hide的,三方APP沒法玩…

/**
  * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
  *
  * @hide
  */
 public AsyncTask(@Nullable Handler handler) {
        this(handler != null ? handler.getLooper() : null);
    }
    
/**
  * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
  *
  * @hide
  */
public AsyncTask(@Nullable Looper callbackLooper) {

# JobService和Service對比

https://blog.csdn.net/allisonchen/article/details/79282651


# Activity用SharedPreferences保存數據,大小有沒有限制?

Sp的底層是由xml實現的,操作Sp的過程就是xml的序列化和解析的過程。xml是儲存在磁盤上的,因此考慮到IO速度問題,sp不適宜頻繁操作。同時序列化xml就是將內存中的數據寫到xml文件中。

sp在創建的時候會把整個文件全部加載進內存,如果你的sp文件比較大,那麼會帶來幾個嚴重問題:

  1. 第一次從sp中獲取值的時候,有可能阻塞主線程,使界面卡頓、掉幀。(getString會等待sp加載完)
  2. 解析sp的時候會產生大量的臨時對象,導致頻繁GC,引起界面卡頓。
  3. 這些key和value會永遠存在於內存之中,佔用大量內存。

SharedPreference的commit和apply:

  1. commit和apply雖然都是原子性操作,但是原子的操作不同,commit是原子提交到Disk,所以從提交數據到存在Disk中都是同步過程,中間不可打斷。

  2. 而apply方法的原子操作是原子提交的內存中,而非數據庫,所以在提交到內存中時不可打斷,之後再異步提交數據到Disk中,因此也不會有相應的返回值。

  3. 所有commit提交是同步過程,效率會比apply異步提交的速度慢,但是apply沒有返回值,永遠無法知道存儲是否失敗。

  4. 在不關心提交結果是否成功的情況下,優先考慮apply方法。

注:SP不能用於跨APP通信,SP是與UID綁定的,通UID的不同進程可以共享,跨進程使用ContentProvider


# Intent傳遞數據大小限制

Intent傳遞的最大數據量是1M,超過該大小就會報異常。


# assest文件夾裏放文件,對於文件的大小有沒有限制?

assets目錄更像一個附錄類型的目錄,Android不會爲這個目錄中的文件生成ID並保存在R類當中,因此它與Android中的一些類和方法兼容度更低。同時,由於你需要一個字符串路徑來獲取這個目錄下的文件描述符,訪問的速度會更慢。但是把一些文件放在這個目錄下會使一些操作更加方便,比方說拷貝一個數據庫文件到系統內存中。要注意的是,你無法在Android XML文件中引用到assets目錄下的文件,只能通過AssetManager來訪問這些文件。數據庫文件和遊戲數據等放在這個目錄下是比較合適的。另外,網上關於assets和raw的資料都千篇一律了,因此關於這兩者中單個文件大小不能超過1M的錯誤描述也在傳播,即如果讀取超過1M的文件會報"Data exceeds UNCOMPRESS_DATA_MAX (1314625 vs 1048576)"的IOException,還引申出種種解決方案。個人認爲不應該有這樣的限制,爲了驗證這個說法寫了個Demo,發現將近5M的壓縮包在assets和raw中都能正常訪問,因此在這裏糾正一下,理論上只要打包不超過Android APK 50M大小的限制都是沒有問題的。當然了,不排除是Android很早期的時候因爲設備硬件原因aapt在編譯的時候對這兩個文件夾大小做出了限制,如果是這樣,較新版的ADT應該不會出現這種情況。Android2.3以前的版本中可以將文件後綴修改成媒體格式(如jpg, wma, mp3…)即可避免該問題。


# 啓動一個程序,可以主界面點擊圖標進入,也可以從一個程序中跳轉過去,二者有什麼區別?

是因爲啓動程序(主界面也是一個app),發現了在這個程序中存在一個設置爲:<category android:name="android.intent.category.LAUNCHER" /> 的Activity,所以桌面會將ICON顯示出來,當點擊該ICON時,發出如下Intent:

Intent intent = mActivity.getPackageManager().getLaunchIntentForPackage(packageName);
mActivity.startActivity(intent);

因此從桌面跳轉的intent是與該icon對應launcher的Activity;而從另一個程序中跳轉,直接指定Intent中指定Action或者Component即可,本質是一樣的,都是根據設定的Intent來進行跳轉。


# 程序之間的親和性(affinity)

默認情況下一個應用的所有Activity都是具有相同的affinity,都是從application中繼承,application的affinity默認就是manifest的包名。affinity對Activity來說,就像是身份證一樣,可以告訴所在的Task,自己屬於其中的一員。
應用場合:
1). 根據affinity重新爲Activity選擇合適的宿主Task
2). 與allowTaskReparenting屬性配合
3). 啓動Activity使用Intent設置了FLAG_ACTIVITY_NEW_TASK標記


# 橫豎屏切換時候Activity的生命週期。(需要驗證)

1). 不設置Activity的android:configChanges時,切屏會重新調用各個生命週期
2). 設置Activity的android:configChanges="orientation"時,切屏還是會重新調用各個生命週期,切橫、豎屏時只會執行一次
3). 設置Activity的android:configChanges="orientation|keyboardHidden|screenSize "時,切屏不會重新調用各個生命週期,只會執行onConfigurationChanged方法


# AIDL的全稱是什麼?如何工作?(看一下系統編譯成的.java文件)

全稱是:Android Interface Define Language

編譯期生成 .java 文件,實質上是通過binder來進行IPC的。AIDL可以進行不同進程,不同APP之間的通信,只需要獲取服務端Service,然後通過AIDL提供的接口進行IPC調用。AIDL支持基本類型,String類型,自定義的類型需要實現Parcelable,並定義.aidl文件聲明該類型變量,如:
User.java 實現Parcelable,User.aidl 中聲明 parcelable User。然後就可以在其他AIDL中使用了。


# AIDL 定向tag:in,out,inout

https://blog.csdn.net/luoyanglizi/article/details/51958091


# dvm的進程和linux的進程,應用程序的進程是否是同一個概念

dvm的進程是dalvik虛擬機進程,每個Android應用程序都運行在虛擬機進程中。Android系統會爲每個應用程序分配一個單獨的uid,每個dvm進程都是linux中的一個進程。


# ImageLoader的三級緩存

Lrucache,DiskLrucache,網絡緩存


# 圖片壓縮

1). 質量壓縮方法(ByteArrayOutputStream, ByteArrayInputStream,Bitmap.compress)
2). 按比例壓縮 -> 按質量壓縮

https://blog.csdn.net/pbm863521/article/details/74391787


# Handler post Runnable 和 普通 Message 的區別

Runnable 也會被包裝成Message傳遞給MessageQueue,但 Message.callback 是該Runnable。在dispatchMessage時,首先會檢查msg.callback,如果存在,則直接回調該方法,也就是run()。

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

# Messenger實現進程間通信

Messenger可以通過Handler或者IBinder進行初始化
bindService -> 初始化Messenger(IBinder)
Messenger.send -> IMessenger.send -> Handler.sendMessage

https://blog.csdn.net/qq_14978113/article/details/80701588


# ContentProvider實現數據共享

底層實現使用的是Binder,ContentProvider生命週期只有onCreate,在其他應用通過ContentResolver第一次訪問時被調用。ContentProvider提供了query(),insert(),update(),delete() 來操作數據庫,其他應用通過ContentResolver來調用。

https://blog.csdn.net/qq_33750826/article/details/52602402


# 相比ListView,RecyclerView有什麼優勢


# ListView性能優化

ListView的每一個item的顯示都需要調用Adapter中的getView()方法,此時會通過inflate來構造View對象,如果item足夠多,就需要構造很多的view對象,而顯示在當前屏幕中的item是有限的。因此造成了資源浪費和性能損耗。

利用convertView和ViewHolder來達到item的view重用。

https://blog.csdn.net/piracy5566/article/details/54142465


# 自定義View流程

https://www.cnblogs.com/jiayongji/p/5560806.html


# Touch 事件分發

Activity.dispatchTouchEvent -> PhoneWindow.superDispatchTouchEvent -> DecorView.superDispatchTouchEvent -> ViewGroup.dispatchTouchEvent -> ViewGroup.onInterceptTouchEvent(可選) -> View.dispatchTouchEvent -> OnTouchListener.onTouchEvent -> View.onTouchEvent -> OnClickListener.onClick -> Activity.onTouchEvent

https://www.cnblogs.com/Jackwen/p/5239035.html


# ViewStub

http://blog.51cto.com/weijiancheng/2085997


#如何獲取View寬高

View的寬高和位置必須在View onLayout之後才能獲取,在平常的生命週期中是無法獲取的,如:
Activity的onCreate、onResume方法,Fragment的onCreate、onCreateView、onResume、onShow方法中。

方法一:重寫Activity的onWindowFocusChanged方法:

/**
 * 重寫Acitivty的onWindowFocusChanged方法
 */ 
@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    /**
     * 當hasFocus爲true的時候,說明Activity的Window對象已經獲取焦點,進而Activity界面已經加載繪製完成
     */
    if (hasFocus) {
        int widht = titleText.getWidth();
        int height = titleText.getHeight();
        Log.i(TAG, "onWindowFocusChanged width:" + widht + "   "
                        + "  height:" + height;
    }
}

方法二:監聽onGlobalLayoutListener

/**
 * 爲Activity的佈局文件添加OnGlobalLayoutListener事件監聽,當回調到onGlobalLayout方法的時候我們通過getMeasureHeight和getMeasuredWidth方法可以獲取到組件的寬和高
 */
private void initOnLayoutListener() {
        final ViewTreeObserver viewTreeObserver = this.getWindow().getDecorView().getViewTreeObserver();
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Log.i(TAG, "開始執行onGlobalLayout().........");
                int height = titleText.getMeasuredHeight();
                int width = titleText.getMeasuredWidth();

                Log.i(TAG, "height:" + height + "   width:" + width);
           // 移除GlobalLayoutListener監聽     
                   MainActivity.this.getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
    }

方法三:使用View.post方法獲取組件的寬高

/**
 * 使用View的post方法獲取組件的寬度和高度,這裏用的是異步消息獲取組件的寬高
 * 而這裏的異步消息的執行過程是在主進程的主線程的Activity繪製流程之後,所以這時候可以獲取組件的寬高。
 */
private void initViewHandler() {
        titleText.post(new Runnable() {
            @Override
            public void run() {
                int width = titleText.getWidth();
                int height = titleText.getHeight();

                Log.i(TAG, "initViewHandler height:" + height + "  width:" + width);
            }
        });
    }

# View的invalidate、postInvalidate

Android中實現view的更新有兩組方法,一組是invalidate,另一組是postInvalidate,其中前者是在UI線程自身中使用,而後者在非UI線程中使用。

View.postInvalidate方法依然是通過Handler的方式將INVALIDATE消息拋到UI線程中處理:

public void postInvalidate() {
        postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
        // We try only with the AttachInfo because there's no point in invalidating
        // if we are not attached to our window
        if (mAttachInfo != null) {
            Message msg = Message.obtain();
            msg.what = AttachInfo.INVALIDATE_MSG;
            msg.obj = this;
            mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
        }
    }

# View的繪製過程和事件分發機制

Android屏幕 View
在這裏插入圖片描述

View繪製第一步:onMeasure,爲整個View樹計算實際的大小,然後設置實際的高和寬,每個View控件的實際寬高都是由父視圖和自身決定的。實際的測量是在onMeasure方法進行。
在這裏插入圖片描述
View繪製第二步:onLayout,ViewGroup的onLayout()方法是一個抽象方法,所有ViewGroup的子類都必須重寫這個方法,重載onLayout的目的是安排其children在父View的具體位置
在這裏插入圖片描述
View繪製第三步:onDraw
在這裏插入圖片描述


# Vsync、SurfaceFlinger

Android中,Client測量和計算佈局,SurfaceFlienger(server)用來渲染繪製界面,client和server的是通過匿名共享內存(Ashmem)通信。

每個應用和SurfaceFlienger之間都會創建一個SharedClient,一個SharedClient最多可以創建31個SharedBufferStack,每個surface對應一個SharedBufferStack,也就是一個Window。也就意味着,每個應用最多可以創建31個窗口

FPS就是Frame Per Second(每秒的幀數)的縮寫,我們知道,FPS>=60時,我們就不會覺得動畫卡頓。當FPS=60時是個什麼概念呢?1000/60≈16.6,也就是說在大概16ms中,我們要進行一次屏幕的刷新繪製。

Vsync是垂直同步的縮寫。這裏我們可以簡單的理解成,這就是一個時間中斷。Vsync每16ms一次,那麼在每次發出Vsync命令時,CPU都會進行刷新的操作。也就是在每個16ms的第一時間,CPU就會想贏Vsync的命令,來進行數據刷新的動作。CPU和GPU的刷新時間,和Display的FPS是一致的。因爲只有到發出Vsync命令的時候,CPU和GPU纔會進行刷新或顯示的動作。


# 進程,虛擬機,應用

每個APP啓動時系統會分配一個虛擬機來執行該Application,當APP的某個組件聲明瞭":remote"時,系統會再開一個虛擬機來啓動Application和該組件。

Android啓動多進程(非Native進程)時,Application會被多次創建(可以這麼理解:運行在同一個進程中的組件是屬於同一個虛擬機和同一個Application的,運行在進程中的組件是屬於兩個不同的虛擬機和Application的)

注:需要自己驗證一下

https://blog.csdn.net/young_xiaot/article/details/51295783


# 進程優先級

前臺進程,後臺進程


# BroadcastReceiver優先級

普通廣播(Normal Broadcast):用sendBroadcast()方法發送。
普通廣播是完全異步的,邏輯上可以在同一時刻被所有匹配的接受者接收到,消息傳遞效率高,缺點是接受者不能將處理結果傳遞給下一個接收者,也無法終止廣播傳播。

有序廣播(Ordered Broadcast):用sendOrderedBroadcast()方法發送。
有序廣播的接收者們將按照事先生命的優先級依次接收,數越大優先級越高(取值範圍:-1000~10000),優先級可以聲明在<intent-filter android:priority=“n”…/>,也可以調用IntentFilter對象的setPriority設置。並且接收者可以終止傳播(調用abortBroadcast()方法即可終止),一旦終止後面接收者就無法接受廣播。另外,接受者可以將處理結果存入數據(可通過setResultExtras(Bundle)方法將數據存入Broadcast),當做Broadcast再傳遞給下一級接收者(可通過代碼Bundle bundle = getResultExtras(true)獲取上一級傳遞過來的數據)。

短信攔截原理:系統收到短信,發出的Broadcast屬於有序廣播,程序就可以通過設定優先級先接收到通知,然後終止傳遞。


# Service是否在main thread中執行

Service不是獨立的進程,也不是獨立的線程,它是依賴於應用程序的主線程的,也就是說,在更多時候不建議在Service中編寫耗時的邏輯和操作(比如:網絡請求,拷貝數據庫,大文件),否則會引起ANR。

如果想在服務中執行耗時的任務。有以下解決方案:

1). 在service中開啓一個子線程
2). 可以使用IntentService異步管理服務

Service 和 Activity 在同一個線程,對於同一 app 來說默認情況下是在同一個線程中的 main Thread (UI Thread)


# Service如何防止被LMK殺掉,如何提高Service的進程優先級

1). 在onStartCommand方法中:return Service.START_STICKY,這樣service被殺後會被重建
2). 在AndroidManifest中設置Service的優先級 android:priority="2147483647",提高進程的優先級
3). 在onStartCommand方法中設置爲前臺進程(設置Notification)
4). 在Activity中設置循環發送鬧鐘(AlarmManager.setRepeating(…))來startService


# Alarm和普通的新開線程定時的區別及優點

1). 在設備進入doze情況下,線程定時任務會被暫停,因此可能會失效,Alarm由於設定了RTC_WAKEUP會被精確喚醒。

2). 新開線程會長時間持有WakeLock,導致系統無法正常休眠,耗電。


# Android 虛擬機Heap內存,Native內存

Android內存主要分爲兩部分:VM heap 和 Native heap。常說的Android OOM 指的是虛擬機Heap內存超過了虛擬機最大Heap內存大小。而Native heap內存不受虛擬機的規定,只要不達到RAM的大小即可。(但O上對native heap的大小應該做了限制,最大應該不能超過1.26g左右)


# Bitmap 內存的位置

Android N 上Bitmap的像素點數據與bitmap對象都是分配到dalvik heap,而Android O 上Bitmap的像素點數據是分配在native heap中,因此在Android O加載大量的Bitmap並不會導致應用OOM,但是有一點要注意,android O對應用native使用的空間也做了限制(不確定是O新增的還是原來就有),當應用佔用的native空間到一定程度時(我本地驗證是1.26G),再調用BitmapFactory.decodeFile()方法時,會直接返回null。


# JNI中如何使用多線程

  1. 在JNI_OnLoad中,保存JavaVM*,這是跨線程的,持久有效的,而JNIEnv*則是當前線程有效的。一旦啓動線程,用AttachCurrentThread方法獲得env。
  2. 通過JavaVM*和JNIEnv能夠查找到jclass。
  3. 把jclass轉成全局引用,使其跨線程。
  4. 然後就能夠正常地調用你想調用的方法了。
  5. 用完後,別忘了delete掉創建的全局引用和調用DetachCurrentThread方法。
JNIEnv* g_env;  // JNIEnv指針只在與它相關聯的線程裏是有效的 
jobject g_thiz;
JavaVM *g_jvm; 
void* thread_get_str(void * argv) {
	(g_jvm)->AttachCurrentThread(&g_env, NULL);  // 綁定當前線程和JVM,複製JVM的一部分環境變量
	LOGE("log in another thread!!!!!!!!!!");
	jclass clazz = (g_env)->GetObjectClass(g_thiz);
	jfieldID fid = (g_env)->GetFieldID(clazz, "mInstanceName", "Ljava/lang/String;");
	jstring jstr = (jstring)(g_env)->GetObjectField(g_thiz, fid);
	LOGE("this message is from other thread of c++ %s", (g_env)->GetStringUTFChars(jstr, NULL));
	(g_jvm)->DetachCurrentThread();
}
 
void threadTest(JNIEnv* env, jobject thiz) {
	if (g_thiz) {
		(g_env)->DeleteGlobalRef(g_thiz);
	}
	g_thiz = (env)->NewGlobalRef(thiz);  // 將jobject保存爲全局引用
	g_env = env;  					     // 將JNIEnv保存爲全局引用
	pthread_t thread;
	pthread_create(&thread, NULL, thread_get_str, NULL);
}

# JVM、Dalvik VM、ART(Android Runtime)

JVM: Java Virtual Machine,是計算機硬件的一層軟件抽象,JAVA在編譯後會生成類似於彙編語言的JVM字節碼.class文件,與C語言編譯後產生的彙編語言不同的是,C編譯成的彙編語言會直接在硬件上跑,但JAVA編譯後生成的字節碼是在JVM上跑,需要由JVM把字節碼翻譯成機器指令,才能使JAVA程序跑起來。

DVM: Dalvik Virtual Machine,是安卓中使用的虛擬機,所有安卓程序都運行在安卓系統進程裏,每個進程對應着一個Dalvik虛擬機實例。他們都提供了對象生命週期管理、堆棧管理、線程管理、安全和異常管理以及垃圾回收等。與JVM不同,它執行的是.dex 或者是.odex文件。

JIT(Just in Time): javac把程序源碼編譯成JAVA字節碼,JVM通過逐條解釋字節碼將其翻譯成對應的機器指令,逐條讀入,逐條解釋翻譯,執行速度必然比C/C++編譯後的可執行二進制字節碼程序慢。JIT會在運行時分析應用程序的代碼,識別哪些方法可以歸類爲熱方法,這些方法會被JIT編譯器編譯成對應的彙編代碼,然後存儲到代碼緩存中,以後調用這些方法時可以直接使用代碼緩存中已編譯好的彙編代碼。這能顯著提升應用程序的執行效率。(安卓Dalvik虛擬機在2.2中增加了JIT)

AOT(Ahead of Time): 類似C/C++這類語言,編譯器在編譯時直接將程序源碼編譯成目標機器碼,運行時直接運行機器碼。

ART: 採用AOT,在應用安裝時進行一次預編譯,將.dex或者.odex轉換成本地機器碼(.oat文件)存儲在本地,這樣在運行時可以直接執行。Android 5.0上正式使用了ART。

JVM和DVM的區別:

1). JAVA虛擬機運行的是JAVA字節碼.class,Dalvik虛擬機運行的是Dalvik字節碼.dex

AVA程序經過編譯,生成JAVA字節碼保存在class文件中,JVM通過解碼class文件中的內容來運行程序。而DVM運行的是Dalvik字節碼,所有的Dalvik字節碼由JAVA字節碼轉換而來,並被打包到一個DEX(Dalvik Executable)可執行文件中,DVM通過解釋DEX文件來執行這些字節碼。

2). Dalvik可執行文件體積更小

class文件中包含多個不同的方法簽名,如果A類文件引用B類文件中的方法,方法簽名也會被複制到A類文件中(在虛擬機加載類的連接階段將會使用該簽名鏈接到B類的對應方法),也就是說,多個不同的類會同時包含相同的方法簽名,同樣地,大量的字符串常量在多個類文件中也被重複使用,這些冗餘信息會直接增加文件的體積

Dalvik虛擬機,SDK中有個dx工具負責將JAVA字節碼轉換爲Dalvik字節碼,dx工具對JAVA類文件重新排列,將所有JAVA類文件中的常量池分解,消除其中的冗餘信息,重新組合形成一個常量池,所有的類文件共享同一個常量池,使得相同的字符串、常量在DEX文件中只出現一次,從而減小了文件的體積。

3). JVM基於棧,DVM基於寄存器

JAVA虛擬機基於棧結構,程序在運行時虛擬機需要頻繁的從棧上讀取寫入數據,這個過程需要更多的指令分派與內存訪問次數,會耗費很多CPU時間。

Dalvik虛擬機基於寄存器架構,數據的訪問通過寄存器間直接傳遞,這樣的訪問方式比基於棧方式要快很多。

DVM和ART的區別:

Dalvik執行的是dex字節碼,依靠JIT編譯器去解釋執行;ART在應用安裝時進行預編譯,將dex或者odex轉換成本地機器碼.oat,在運行時直接執行機器指令

ART的優點:系統性能顯著提升;應用啓動更快、運行更快、體驗更流暢、觸感反饋更及時;續航能力提升;支持更低的硬件

ART的缺點:更大的存儲空間佔用,可能增加10%-20%;更長的應用安裝時間


# Dalvik虛擬機內存分配參數

dalvik.vm.heapstartsize

堆分配的初始大小,調整這個值會影響到應用的流暢性和整體ram消耗。這個值越小,系統ram消耗越慢,但是由於初始值較小,一些較大的應用需要擴張這個堆,從而引發gc和堆調整的策略,會應用反應更慢。相反,這個值越大系統ram消耗越快,但是程序更流暢。

dalvik.vm.heapgrowthlimit

極限堆大小,dvm heap是可增長的,但是正常情況下dvm heap的大小是不會超過dalvik.vm.heapgrowthlimit的值。如果受控的應用dvm heap size超過該值,則將引發oom。

dalvik.vm.heapsize

使用大堆時,極限堆大小。一旦dalvik heap size超過這個值,直接引發oom。在android開發中,如果要使用大堆,需要在manifest中指定android:largeHeap爲true。這樣dvm heap最大可達dalvik.vm.heapsize。

dalvik.vm.heaptargetutilization:

[0.75] 可以設定內存利用率的百分比,當實際的利用率偏離這個百分比的時候,虛擬機會在GC的時候調整堆內存大小,讓實際佔用率向個百分比靠攏。


虛擬機的內存分配過程:

  1. 首先判斷一下需要申請的size是不是過大,如果申請的size超過了堆的最大限制,則轉入步驟6

  2. 嘗試分配,如果成功則返回,失敗則轉入步驟3

  3. 判斷是否gc正在進行垃圾回收,如果正在進行則等待回收完成之後,嘗試分配。如果成功則返回,失敗則轉入步驟4

  4. 自己啓動gc進行垃圾回收,這裏gcForMalloc的參數是false。所以不會回收軟引用,回收完成後嘗試分配,如果成功則返回,失敗則轉入步驟5

  5. 調用dvmHeapSourceAllocAndGrow嘗試分配,這個函數會擴張堆。所以heap startup的時候可以給一個比較小的初始堆,實在不夠用再調用它進行擴張

  6. 進入回收軟引用階段,這裏gcForMalloc的參數是ture,所以需要回收軟引用。然後調用dvmHeapSourceAllocAndGrow嘗試分配,如果失敗則拋出OOM。


# class,dex,odex,oat

.class文件:由javac工具對.java文件編譯生成的字節碼文件

.dex:由adt工具對.class文件編譯生成的字節碼文件,是對.class的一種優化,由dvm執行

.odex:在應用安裝過程中PMS通過dexopt工具對apk文件中的dex字節碼進行優化,分離了程序資源和可執行文件,進行預編譯處理。加快了軟件加載速度。

.oat:在應用安裝過程中由PMS通過dex2oat工具將apk文件中的classes.dex文件翻譯程本地機器碼。是一種ELF文件,可在ART上執行。

無論是對dex字節碼進行優化,還是將dex字節碼翻譯成本地機器碼,最終得到的結果都是保存在相同名稱的一個odex文件裏面的,但是前者對應的是一個dey文件(表示這是一個優化過的dex),後者對應的是一個oat文件(實際上是一個自定義的elf文件,裏面包含的都是本地機器指令)。通過這種方式,原來任何通過絕對路徑引用了該odex文件的代碼就都不需要修改了。


# Android 熱修復

底層熱替換(AndFix): 每個Java方法在ART虛擬機中都對應一個ArtMethod,ArtMethod記錄了Java方法的所有信息,包括所屬類,訪問權限,代碼執行地址等。在JNI中通過傳入的Java Method對象獲取到對應的ArtMethod,將需要替換方法ArtMethod中的每個成員字段都替換成新ArtMethod中的每個成員字段。(缺點:不同版本Art虛擬機中的ArtMethod結構不同,需要適配;各個廠家對ArtMethod中的結構可能做了修改,因此替換可能出現嚴重的錯誤)。

底層熱替換(Sophix):通過memcpy(src, dest, sizeof(ArtMethod))的方式直接對新舊兩個ArtMethod對象進行內存拷貝,可以忽略虛擬機中ArtMethod的結構問題。sizeof(ArtMethod)可以通過定義兩個Java static方法(direct 方法),在JNI中計算兩個方法索引值的差值來確定。

類加載方案(Qzone,QFix,Tinker): Android ClassLoader 支持加載多個dex文件dexElements,並且順序遍歷該數組進行加載,如果一個類A在classes.dex中被加載,那麼出現在classes1.dex中的同名類A就不會被繼續加載。

CLASS_ISPREVERIFIED:當一個類中引用了另外一個類,則一般要求兩個類來自同一個Dex文件。如果B類引用了A類,且A類和B類在同一個dex文件中,類B被打上CLASS_ISPREVERIFIED標誌。(如果類A需要修復,且新類A與類B不在同一個dex文件中,則當類B引用新類A時就會報錯。)

Qzone的解決方法:使用插樁的方式,構建一個輔助類打包在另一個dex文件中,apk中的所有類都對這個輔助類進行引用,這樣apk中的所有類都不會被打上CLASS_ISPREVERIFIED。將新的補丁類打包成一個新的dex文件並插入到dexElements的第一位。(缺點:這種方案會帶來性能問題。正常情況下,類的校驗和優化都在APK第一次安裝時通過dexopt對所有類進行校驗並打上preverify標記;如果沒有該標記,每次類初始化時都會對需要連接的對象進行校驗和初始化,如果需要加載大量的類,將會很耗時,可能會導致應用啓動時白屏)

Tinker的解決方法:通過自研技術將補丁的dex文件和舊的dex文件進行合併生成一個全新的全量dex文件,並插入到dexElements數組的第一位置,這樣,ClassLoad只會加載新的dex文件,避免了CLASS_ISPREVERIFIED問題。(缺點:由於merge的操作是在java heap上實現的,大量的內存操作可能會導致oom(可以放在JNI中使用native stack內存),並且在應用啓動merge時可能耗時較長,可能會導致卡頓問題)

Sophix:將補丁dex文件命名爲classes.dex,並把原apk文件中的dex文件依次命名爲classes1.dex,classes2.dex…,然後一起打包成一個壓縮文件;使用DexFile.loadDex將該壓縮包加載成DexFile文件並整體替換舊的dexElements數組。


# 內存溢出和內存泄漏有什麼區別?何時會產生內存泄漏?內存優化有哪些方法?

內存溢出(OOM):應用程序申請的虛擬機內存超過系統分配給應用程序的最大內存

內存泄漏:對於已經使用完的內存沒有及時進行釋放

內存抖動:內存頻繁地分配和回收,而頻繁的gc會導致卡頓,嚴重時還會導致OOM。大量小的對象頻繁創建,導致內存碎片,從而當需要分配內存時,雖然總體上還是有剩餘內存可分配,而由於這些內存不連續,導致無法分配,系統直接就返回OOM了。

1). 大量的圖片、音頻、視頻處理,當在內存比較低的系統上也容易造成內存溢出
 建議使用第三方,或者JNI來進行處理

2). Bitmap對象的不正確處理(內存溢出)
 不要在主線程中處理圖片;使用Bitmap對象要用recycle釋放;利用高效的處理大圖的第三方庫
 
3). 非靜態或匿名內部類Handler或Runnable由於持有外部類Activity的引用導致內存泄漏
 方法1. 使用靜態內部類(嵌套類) + 弱引用;
 方法2. 在Activity的onDestroy中調用handler.removeCallbackAndMessages(null),或stop thread即可

4). static關鍵字修飾的變量由於生命週期過長,容易造成內存泄漏
 儘量少使用靜態變量,一定要使用要及時進行制null處理

5). 單例模式造成的內存泄漏
 在context的使用上,應該傳入application的context到單列模式中,這樣就保證了單列的生命週期跟application的生命週期一樣;單例模式應該儘量少持有生命週期不同的外部對象,一旦持有該對象的時候,必須在該對象的生命週期結束前制null

6). 使用ArrayMap及SparseArray:
是android的系統API,是專門爲移動設備而定製的。用於在一定情況下取代HashMap而達到節省內存的目的,對於key爲int的HashMap儘量使用SparceArray替代,大概可以省30%的內存,而對於其他類型,ArrayMap對內存的節省實際並不明顯,10%左右,但是數據量在1000以上時,查找速度可能會變慢。

7). ListView複用
getView裏儘量複用conertView,同時因爲getView會頻繁調用,要避免頻繁地生成對象

8). 少使用枚舉
枚舉變量可能比直接用int多使用2倍的內存。

9). 儘量使用系統資源,系統組件,圖片甚至控件的id

10). 數據相關
序列化數據使用protobuf可以比xml省30%內存,慎用shareprefercnce,因爲對於同一個sp,會將整個xml文件載入內存,有時候爲了讀一個配置,就會將幾百k的數據讀進內存,數據庫字段儘量精簡,只讀取所需字段。

11). 帶有Looper的Thread如HandlerThread在不使用時要即使stop,線程不運行也會佔有內存,大約1M

https://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286910&idx=1&sn=0cd5e183c5f00b0863782e0b63a468d9&chksm=8334cc7cb443456a0e5e51e4930b2c56f4539b9334ff5cd0e3b9e791357f73a1548dafc64ccf&mpshare=1&scene=1&srcid=1112APQr0UHARjldZKxu6oMH&pass_ticket=INb9BmQ5usmUxOCNfDv7PwaT10XzUAQHHBGvfmb9k3i2a%2BM%2BTCiVRNRhhvKb1wFb#rd


# VSS,RSS,PSS,USS

VSS - Virtual Set Size 虛擬耗用內存(包含共享庫佔用的內存)
RSS - Resident Set Size 實際使用物理內存(包含共享庫佔用的內存)
PSS - Proportional Set Size 實際使用的物理內存(比例分配共享庫佔用的內存)
USS - Unique Set Size 進程獨自佔用的物理內存(不包含共享庫佔用的內存)

VSS (reported as VSZ from ps) 是一個進程總共可訪問的地址空間。其大小還包括了可能不在RAM中的內存(比如雖然malloc分配了空間,但尚未寫入)。 VSS 很少被用於判斷一個進程的真實內存使用量。

RSS 是一個進程在RAM中真實存儲的總內存。但是RSS還是可能會造成誤導,因爲它僅僅表示該進程所使用的所有共享庫的大小,它不管有多少個進程使用該共享庫,該共享庫僅被加載到內存一次。所以RSS並不能準確反映單進程的內存佔用情況。

PSS 與RSS不同,它按比例表示使用的共享庫, 例如:如果有三個進程都使用了一個共享庫,共佔用了30頁內存。那麼PSS將認爲每個進程分別佔用該共享庫10頁的大小。 PSS是非常有用的數據,因爲系統中所有進程的PSS都相加的話,就剛好反映了系統中的總共佔用的內存。 而當一個進程被銷燬之後, 其佔用的共享庫那部分比例的PSS,將會再次按比例分配給餘下使用該庫的進程。這樣PSS可能會造成一點的誤導,因爲當一個進程被銷燬後,PSS不能準確地表示返回給全局系統的內存(the memory returned to the overall system)。

USS 是一個進程所佔用的私有內存。即該進程獨佔的內存。 USS是非常非常有用的數據,因爲它反映了運行一個特定進程真實的邊際成本(增量成本)。當一個進程被銷燬後,USS是真實返回給系統的內存。當進程中存在一個可疑的內存泄露時,USS是最佳觀察數據。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述


# APP 瘦身

代碼瘦身:

  1. 移除無用代碼、功能;
  2. 移除無用的庫、避免功能雷同的庫;
  3. 啓用Proguard進行混淆
  4. 縮減方法數;

資源瘦身:

  1. 移除無用的資源文件;
  2. Drawable目錄只保留一份資源;
  3. 圖片進行壓縮;
  4. PNG轉換JPG;
  5. 使用矢量圖;
  6. 使用WebP;
  7. 資源混淆;
  8. 資源在線化;

so瘦身:

  1. 在允許的情況下,針對用戶機型分佈保留特定架構的So;

7Zip壓縮:

  1. 使用7Zip對Apk進行極限壓縮。

其它方式:

  1. 插件化,將Dex與資源文件放在服務端,需要時下載
  2. 通過在 build.gradle配置include來針對每個CPU架構生成單獨的安裝包,按照架構上傳Apk;但是這個方案在國內應用市場幾乎沒有採用的,只能在Google Play上使用。

一點經驗:對Apk進行瘦身,瘦身So以及資源文件是見效最快的操作。瘦身So以及刪除不用的圖片、壓縮圖片之後,Apk會縮減很大的比例;而針對Dex的優化可能作用不會很明顯。

https://www.jianshu.com/p/99f3c09982d4


# Android性能優化

https://www.jianshu.com/u/fdb392adfbed


# Android進程保活策略

提升進程優先級的方式

  1. Activity提權,監聽屏幕的息屏和解鎖,使用一個1個像素的Activity

  2. Service提權,Service通過startForground方法來開啓一個Notification

進程拉活

  1. 通過廣播的方式

  2. 通過Service在onStartCommand的返回值,START_STICK,由系統拉活,在短時間內如果多次被殺死可能就再也啓動不了了

  3. 通過賬戶同步拉活

  4. 通過JobSchedule拉活

  5. 通過Service的bind啓動的方式,雙進程守護拉活

  6. 推送拉活

  7. Native fork子進程的方式拉活


# Android強弱指針

RefBase:C++每個類型需要繼承自該類型。包含incStrong,decStrong,incWeak,decWeak等方法,通過對象自身完成強弱引用的計數。

sp< RefBase >:對象的強引用類型,其中包含了對象的指針。重寫了 operator =,在該方法中:

  1. 創建sp對象(自動創建,如果sp對象不存在)
  2. 調用sp即將引用對象的incStrong()方法,= 右邊對象的強弱引用+1
  3. 調用sp原來引用對象的decStrong()方法,原來引用對象的強弱引用-1

wp< RefeBase >:對象的弱引用類型,爲了解決循環引用導致的內存無法正常釋放問題(對象循環引用時,採用弱引用的方式可以解決)。原理與sp一致:

wp<T>& wp<T>::operator = (T* other)
{
    weakref_type* newRefs =
        other ? other->createWeak(this) : 0;
    if (m_ptr) m_refs->decWeak(this);
    m_ptr = other;
    m_refs = newRefs;
    return *this;
}

強引用可以訪問對象,弱引用無法直接訪問對象,需要通過promote升級爲強引用纔可以訪問對象。


# Binder解析

  1. Binder系列開篇:
    http://gityuan.com/2015/10/31/binder-prepare/
  2. Binder Driver初探:
    http://gityuan.com/2015/11/01/binder-driver/
  3. Binder Driver再探
    http://gityuan.com/2015/11/02/binder-driver-2/
  4. ServiceManager啓動
    http://gityuan.com/2015/11/07/binder-start-sm/ http://gityuan.com/2015/11/07/binder-start-sm/
  5. 獲取ServiceManager
    http://gityuan.com/2015/11/08/binder-get-sm/
  6. 註冊服務(addService)
    http://gityuan.com/2015/11/14/binder-add-service/
  7. 獲取服務(getService)
    http://gityuan.com/2015/11/15/binder-get-service/
  8. Framework層分析
    http://gityuan.com/2015/11/21/binder-framework/
  9. 如何使用Binder
    http://gityuan.com/2015/11/22/binder-use/
  10. 如何使用aidl
    http://gityuan.com/2015/11/22/binder-use/
  11. Binder總結
    http://gityuan.com/2015/11/28/binder-summary/

做股票期貨的,經驗類似,面試太過順利…

Activity的啓動模式

Activity和Fragment數據交互

線程間交互的方法都行,進程間交互的就更可以了,FragmentManager/getActivity()

單例的實現方法

事件分發機制

開源庫用過哪些

自己平時寫項目學習,基本都用過,原理也瞭解;重構項目的資訊模塊用了Retrofit & RxJava,自己思考優化了代碼框架,比如線程切換、頁面查找等,其實弄清楚原理,要改哪裏一目瞭然

項目中Socket是怎麼分包、編碼的?

包類型分爲Text類型、Table類型、資源類型、曲線類型、交互模式數據(曲線類型屬性主要是點,關鍵方法append,可以增量更新數據)

公司自己定義了個Base64編碼,算法當然不要去糾結了

延伸了下壓縮(主要用於Table和曲線類型)和加密(參考HTTPS)

股票的圖怎麼畫?像左右滑動、點擊出現十字光標這些

原理知道即可:自定義View & 觸摸反饋

要了16K,和當初進同花順一樣,當天給了答覆

有贊

重點在kotlin、RN 和 weex,其實也正常,電商類都偏向混合開發,畢竟活動更新很頻繁,和自己的技術棧不是很符合,估計是掛了,也正常,以公司角度肯定最好是找搞過的,以個人角度技術棧對不上肯定也會被壓薪資…

Handler、Looper、MessageQueue的關係

類似微博分享功能適合的launchMode,爲什麼不是singleInstance?

Binder具體的實現原理,數據拷貝次數:代理模式 & 協議,太細的忘了,開發藝術探索、內核剖析、源代碼情景分析裏都看過…

個人覺得學以致用,比如App啓動,沒必要每步都記清楚,知道需要創建ApplicationThread、ActivityThread,然後和冷熱啓動結合想一下,activity的啓動流程和生命週期結合想一下,雖然還沒深入到那一步,但是啓動模式實現肯定也是在AMS的,知識其實是相通的,多想想設計者這樣設計的道理 -> 不過可能也是自己經驗不夠,理解不到位,不過死記真的沒意思…

RxJava:一個請求用完接着再用一個請求(concat)/兩個請求並行使用哪個操作符(zip)

其實場景是很常見的,比如我們也有委託雙重認證,併發請求就更多了,但是RxJava用的少,沒那個意識,資訊那塊的請求很簡單,不過提醒我了,雙重認證可以封裝簡化 -> 可能不行,公司的網絡框架沒那麼解耦的

這種類似問題我是最煩的,確實不會,不過知道就是輸入關鍵字搜索就可以知道的問題

StringBuilder和StringBuffer的區別,StringBuffer的實現原理

HashMap的rehash擴容是怎麼操作的:沒什麼印象了

1). 創建一個新的Entry空數組,長度是原來的2倍

2). 遍歷原Entry數組,把所有的Entry重新Hash到新數組裏。爲什麼要重新Hash呢?因爲長度擴大以後,Hash的規則也隨之改變了

-> 想問的應該是rehash的公式,之前看到過,不過沒去記…

-> 也有可能想知道的是:LoadFactor:HashMap負載因子,默認是0.75f;Capacity:HashMap的當前長度,HashMap的長度必是2的冪

棧虛擬機和寄存器虛擬機差異:聽都沒聽過,不過知道寄存器處理速度是最快的

Retrofit的註解是怎麼解析的:動態代理、AOP

Handler的內存泄漏原因;爲什麼內存釋放不了,MessageQueue持有? -> 被帶溝裏了,肯定知道內部類持有外部引用導致activity無法釋放;想的是線程是GC Root,Handler正在運行,會導致activity無法釋放,所以被引導,順勢說了MessageQueue持有了activity

JSBridge安全漏洞解決方法:知道有安全問題,但是沒仔細研究

ClassLoader雙親委派實現原理

IntentService的實現原理

平時用過哪些框架:RxJava和Retrofit重構了資訊模塊

淘客吧

View的繪製流程、承載的數據結構、樹的好處

算法題:把0排到最前面

事件分發機制

MVC MVP MVVM的區別

ClassLoader的過程,ClassLoader的好處

Handler內存泄露

多線程機制

HTTPS工作原理

數據怎麼壓縮,數據的安全

插件化原理

ARoutet原理

組件化通信

內存優化做過哪些

討論了下負載均衡

系統啓動流程和activity啓動流程

成長最大的階段

阿拉丁,一面

領導不在,同學內推的,面試官說進阿拉丁有點屈才,該試試大廠。我也想,但是不認識內推的人…

HTTP和HTTPS區別

HTTPS流程,項目中安全和壓縮處理

Binder機制

ClassLoader機制

對架構的理解

OkHttp原理,怎麼把參數組裝的

事件分發機制

HashMap的哈希散列實現,線程安全嗎,爲什麼?

ArrayList和Vector擴容的區別

HashTable,ConcurrentHashMap怎麼實現線程安全

jvm內存模型,新生代和老年代的比例?

新生代裏怎麼劃分?好處?

熟悉哪些數據結構?

設計模式在源碼和項目中的使用

單例模式的寫法,思路

java8對hashmap的優化

hashmap和hashset區別,hash怎麼散列的

tcp三次握手

頭條,一面

項目中成就最大的部分

對Thread的理解?線程狀態?阻塞和運行狀態區別?

鎖的種類,什麼是自旋鎖,ReentrantLock?

HashMap原理?

HandlerThread原理和使用場景?

-> IntentService

線程池怎麼實現,阻塞隊列原理?

-> 阻塞隊列實現沒去了解,下意識提了下Looper.loop循環,後面一看還真是,程序員的直覺

Fragment初始化參數調用哪個方法?

Fragment懶加載怎麼實現?

事件分發機制,分析3層View包裹,點擊click

自定義View怎麼繪製,注意點?

網絡框架怎麼重構封裝的?

TCP三次握手

對資訊進行了MVP封裝,講講MVC和MVP的理解

爲什麼使用Bundle不用HashMap傳輸數據?

Serialable 和 Parcelable區別

glide的圖片三級緩存

交互模式

愛庫存

面完有個面產品的漂亮小姐姐搭話,心裏美滋滋

大都問的項目,每個點延伸講了很多,所以問的個數不多

Service熟悉嗎?

Service進程保活?

MVC和MVP

發現大都問的相似,我都想停更了…

祝好,持續更新…

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