Android 小知識點(一)

  1. OOM 可以被try catch 麼?
    OOM 屬於Error 發生在try catch 可以被catch ,但不推薦這麼做
    Error(錯誤)是系統中的錯誤,程序員是不能改變的和處理的,是在程序編譯時出現的錯誤,只能通過修改程序才能修正。一般是指與虛擬機相關的問題,如系統崩潰,虛擬機錯誤,內存空間不足,方法調用棧溢等。對於這類錯誤的導致的應用程序中斷,僅靠程序本身無法恢復和和預防,遇到這樣的錯誤,建議讓程序終止。 常見 OutofMemoryError StackOverflowError

Exception(異常)表示程序可以處理的異常,可以捕獲且可能恢復。遇到這類異常,應該儘可能處理異常,使程序恢復運行,而不應該隨意終止異常。常見 NullPointerException IllegalArgumentException
1.android:clipChildren =false 的父佈局裏面放一個webview 有bug

  1. Serializable與Parcable的區別。 在什麼情況下需要用到序列化和反序列化,Serializable中爲什麼要設置UID,設置UID與不設置UID值的區別和影響?
    Serializable 默認會爲你生成一個Uid 當類結構發生變化的時候Uid 會發生變化, 如果序列化到一個文件之後,修改了類結構,反序列化的時候,會認爲不是同一個類 java.io.InvalidClassException FC , 如果自己制定Uid 不會發生變化,可以避免fc ,但是隻能恢復之前結構裏有的數據

  2. 爲什麼要使用動態代理 需求模型:在一個類的所有方法執行前後打印log ,靜態代理的做法,需要根據被代理的類的方法創建n個方法,但是動態代理,通過反射實現,不需要關心,被代理類 的方法有多少什麼樣子,統統走到handler 的invoke 方法中 ,能夠做統一的處理,還有一些比如說類似於jar 包中的類,我們不知道具體實現的方法是哪些,可以通過動態代理處理

/**
 * 本來用於驗證動態代理的使用場景,動態代理比靜態代理優勢在哪裏
 * 需求場景 : 在某個方法執行前後打印log
 *
 */
public class InvokeProxy {
    private ICalculator target;
    public static String TAG="fs InvokeProxy TAG";

    public InvokeProxy(ICalculator target) {
        this.target = target;
    }

    public ICalculator getIcalculatorProxy() {
        ClassLoader classLoader = target.getClass().getClassLoader();
        Object proxyInstance = Proxy.newProxyInstance(classLoader, new Class[]{ICalculator.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Log.d(TAG, "invoke: " + Arrays.asList(args));
                Object invoke = method.invoke(target, args);
                Log.d(TAG, "invoke: " + invoke);
                return invoke;
            }
        });
        return (ICalculator) proxyInstance;
    }
}
  1. 關於intentFlag FLAG_ACTIVITY_NEW_TASK
    如果在不指定棧(比如從manifest中) 是不會獨立創建一個棧的,可能是安卓避免用戶過多的創建棧,只有在制定棧的時候才生效,否則相當於沒有加這個flag,詳情查看谷歌文檔
    [https://developer.android.google.cn/guide/components/activities/tasks-and-back-stack?hl=zh_cn#Affinities]

  2. adb的常用命令
    https://juejin.im/entry/57c00fe4c4c971006179838a#%E8%B0%83%E8%B5%B7-activity

1)啓動一個activity
adb shell am start -n com.android.email/com.kingsoft.email2.ui.MailActivityEmail
2)複製文件

adb pull /sdcard/01.png C:\Users\fushuang\Desktop\test.png
3)導入文件
adb push C:\Users\fushuang\Desktop\數倉1期埋點20190719.xlsx /sdcard/test11.xlsx
4)輸入文本
adb shell input text "aaaaa"
5)打印activity 棧信息
adb shell dumpsys activity | findstr Run
6)adb 截屏
adb shell screencap -p /sdcard/01.png
7)發送廣播
adb shell am broadcast -a com.kingsoft.email.action.battery_setting_changed -p com.android.email

  1. 關於Intent.FLAG_ACTIVITY_SINGLE_TOP
    Intent 的Flag 只適用於ams 將activity 完全初始化完成之後生效,一下情況是無法生效的,仍然會啓動三個同樣的界面在棧頂,同樣 設置android:launchMode="singleTop"也不會生效。
    如果需要產生嚴格的SingleTop的現象,需要Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP
        final Intent intent = new Intent(this, Empty2Activity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        startActivity(intent);
        startActivity(intent);
        startActivity(intent);
  1. Intent.FLAG_ACTIVITY_CLEAR_TOP 並不等同於 android:launchMode="singleTask"
    棧內A,B,C,D 啓動 B ,對於C,D是沒有區別的,但是Intent.FLAG_ACTIVITY_CLEAR_TOP 會導致B Destory 然後onCreate 但是 android:launchMode="singleTask" 不會重新創建,只會onNewIntent 。要想通過設置Flag 實現一樣的效果,需要 Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP 這樣才能防止重新創建

  2. Intent 在傳遞枚舉(enum) 的時候通過Serialiazeable傳遞, 但是反序列化之後並沒有創建新的對象,對象地址不變,但是在傳遞Class 的時候,發序列化地址會變,只是內容被clone 了. 所以比較枚舉反序列化前後的值可以用==判斷,返回是true ,但是class == 判斷前後的值,返回爲false

  1. 一個線程崩潰會影響到其他線程麼?
    經過試驗發現其實是不會的,之所以安卓fc 會導致整個應用結束是因爲Thread 的DefaultUncaughtExceptionHandler 中應該是作了終結進程的操作
android.os.Process.killProcess(android.os.Process.myPid())

當我們自己設置主線程DefaultUncaughtExceptionHandler 之後,主線程發生崩潰,子線程可以繼續工作,不受影響,但是主線程在接收事件之後會發生anr, 考慮可能是主線程事件響應機制中做了某些判斷.可能有主線程是否fc的標記位

  1. LinkCanary
    linkcanary 中依賴的aar 文件,在AS 中可能不會看到清單文件,因爲as 在展示有些包結構的時候展示的並不完整,在文件中查看會看到aar 中存在清單文件,並且在其中聲明瞭所需的Service 和Activity , 在編譯的時候會merge 到最終的Manifast 裏.
    另外LinkCanary 中使用弱引用機制檢測內存泄漏,將Activity 通過弱引用持有.弱引用有一個特點,如果被弱引用指向的對象,有一個強引用持有的話,這種情況gc是不會回收弱引用的,所以如果發生泄漏,弱引用get() 方法是一直可以獲取到activity或者fragment 的,反之,如果不存在泄漏,那麼弱引用作爲唯一的引用,是一定會被gc發現回收的.
    在發生泄漏之後會調用系統VM 方法生成hprof 文件,然後讀取通過算法分析,算出泄漏對象到達gc root 的最短路徑,,關於計算算法可能纔是linkcanary 核心的東西, 然後交由展示頁面展示

如果這個 activity 可以被回收,那麼弱引用就會被添加到 ReferenceQueue 中。
等待主線程進入 idle後,通過一次遍歷,在 ReferenceQueue 中的弱引用所對應的 key 將從 retainedKeys 中移除,說明其沒有內存泄漏。
如果 activity 沒有被回收,先強制進行一次 gc,再來檢查,如果 key 還存在 retainedKeys 中,說明 activity 不可回收,同時也說明了出現了內存泄漏。

  1. 關於啓動模式:
    使用Intent.FLAG_ACTIVITY_CLEAR_TOP + Intent.FLAG_ACTIVITY_SINGLE_TOP時調用了onNewIntent方法,複用了MainActivity,說明他們可以實現和singleTask一樣的效果
    Intent.FLAG_ACTIVITY_CLEAR_TOP 不等同於singleTask 啓動模式,因爲不會調用onNewIntent

  2. 關於Android 文件路徑獲取

  • 文件包內路徑(隨app 刪除而刪除)
Context.getCacheDir():               /data/data/com.learn.test/cache
Context.getFilesDir():               /data/data/com.learn.test/files
Context.getFileStreamPath(""):       /data/data/com.learn.test/files
Context.getFileStreamPath("test"):   /data/data/com.learn.test/files/test
  • SDcard 外部存儲(也可以是內置Sdcard)
//外部存儲應有私有
Context.getExternalCacheDir():                           /storage/emulated/0/Android/data/com.learn.test/cache
Context.getExternalFilesDir(""):                         /storage/emulated/0/Android/data/com.learn.test/files
Context.getExternalFilesDir("test"):                     /storage/emulated/0/Android/data/com.learn.test/files/test
Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES):    /storage/emulated/0/Android/data/com.learn.test/files/Pictures

###以上在包名內部的均不需要權限申請
//外部存儲public share 目錄
//高版本需要動態申請權限
Environment.getExternalStorageDirectory():                     /storage/emulated/0
Environment.getExternalStoragePublicDirectory(""):             /storage/emulated/0
Environment.getExternalStoragePublicDirectory("test"):         /storage/emulated/0/test
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES):  /storage/emulated/0/Pictures
  1. 子線程是否能更新UI
    在ViewRootImpl 中 記錄初始化線程mTread
  void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

checkTread會在onLayout 等方法中調用,如果settext 不引起laytou 變化,可以設置,不會fc

Android 8.0 適配
1.通知需要有Channel 很多本來對Notification 的設置,都轉換到了對Channel 設置,8.0之後對通知有所簡化,副文本標題取消顯示,顯示通知時間需要單獨設置 否則默認不顯示時間
2.對於電量消耗的優化,安卓採用對後臺Service進行限制,進入後臺60s 之後,service 會調用自身onDestory ,調用之後,內部沒有完成的線程還會繼續執行(可能產生內存泄漏,儘量在onDestory中stopSelf) 但是不能再在Service中startService ,防止服務互相喚醒或者啓動更多的服務消耗電量.
對於之前Service方案,可採用一下兩種替代方案
(1) 採用 startForegroundService(new Intent(getBaseContext(),ServiceTest.class)); 方法,開啓一個前臺Service, 在開啓後的5s後,必須調用 startForeground(1, notification); 但目前8.0版本 沒有設置channel 的通知不會被顯示,所以只是對於系統來說,屬於前臺Service,對於用戶來說,不會有其他的表現,估計以後版本,會修復吧.儘量還是按照會在界面展示考慮
(2)採用JobService 代替Service ,JobService Add in Api 21. 用於在一些特定時刻觸發操作,類似於定時任務.內部封裝了方便的監聽網絡變化,開機 (之前用廣播監聽在高版本也會失效,也需要用JobService 替代) . 通過setOverrideDeadline 或者setMinimumLatency 設置一個很短的時間,來讓他立即執行,可以起到同普通Service 一樣的效果.
(3) 對於廣播: 隱式廣播(通過Action 啓動的) ,在未指定包名的情況下.無法全局廣播.

  1. 關於postInvalidate() requestLayout()
    requestLayout() 只是通知父容器位置變化,重新進行measure 和layout 不會重新繪製ondraw (但實際上經過測試發現,第一次是會調用一次ondraw 的,但是後續就不會調用了,只會遞歸的向上調用,比如Viewgroup 調用 requestLayout 只會引起他的parent measure 和layout ,子view 並不會重新測量)
    postInvalidate() 不關心位置變化,不走measure 和layout 只走ondraw,並且不會引起遞歸調用,調用哪個控件的ondraw 就只是ondraw 他本身

  2. SharePreferance 是否是線程安全的?
    SharedPreferences是線程安全的,它的內部實現使用了大量synchronized關鍵字SharedPreferences不是進程安全的第一次調用getSharedPreferences會加載磁盤 xml 文件(這個加載過程是異步的,通過new Thread來執行,所以並不會在構造SharedPreferences的時候阻塞線程,但是會阻塞getXxx/putXxx/remove/clear等調用),但後續調用getSharedPreferences會從內存緩存中獲取。 如果第一次調用getSharedPreferences時還沒從磁盤加載完畢就馬上調用 getXxx/putXxx, 那麼getXxx/putXxx操作會阻塞,直到從磁盤加載數據完成後才返回所有的getXxx都是從內存中取的數據,數據來源於SharedPreferences.mMapapply同步回寫(commitToMemory())內存SharedPreferences.mMap,然後把異步回寫磁盤的任務放到一個單線程的線程池隊列中等待調度。apply不需要等待寫入磁盤完成,而是馬上返回commit同步回寫(commitToMemory())內存SharedPreferences.mMap,然後如果mDiskWritesInFlight(此時需要將數據寫入磁盤,但還未處理或未處理完成的次數)的值等於1,那麼直接在調用commit的線程執行回寫磁盤的操作,否則把異步回寫磁盤的任務放到一個單線程的線程池隊列中等待調度。commit會阻塞調用線程,知道寫入磁盤完成才返回MODE_MULTI_PROCESS是在每次getSharedPreferences時檢查磁盤上配置文件上次修改時間和文件大小,一旦所有修改則會重新從磁盤加載文件,所以並不能保證多進程數據的實時同步從 Android N 開始,,不支持MODE_WORLD_READABLE & MODE_WORLD_WRITEABLE。一旦指定, 直接拋異常

  3. 方法時間性能優化
    android.os.Debug.startMethodTracing(String traceName);
    android.os.Debug.stopMethodTracing();


  4. 關於切換語言 導致棧內activity 重新創建的問題
    如果棧裏有兩個activity 如果切換語言導致界面重新創建 , 則兩個activity 創建時間是不同的,系統只會在activity 變爲可見的時候重新創建,如果在A1 oncreate 中啓動了A2 A1 onActivityResult 中finish() A1
    那麼 棧內A1 A2 的情況下,切換語言,a2 finish 生命週期如下
    A1 onDestroy A1onCreate (先重新創建)
    A1onActivityResult (中執行finish)
    A2 onCreate (由此可見,是A2 先創建,爲了resume 做好準備之後A1才銷燬的)
    A1 stop destory
    A2 resume()

19.關於.9 png問題
.9 png 的內容區域(右邊黑線)如果不是全部圖片高度,比如上面少個100 px,那麼把這個設置給一個LinearLayout 這個drawable 。會導致內容變高,比原來的高100個像素,這在LinearLayout 的measure 和layout 方法裏應該有體現,沒仔細在源碼裏找,有時間看看

20.Android onTouchEvent event.getY() 是相對於view的getTop的位置麼?
不是的,不是的,getTop並不會計算到translation 的值。是相對於View.getY的偏移量

 public float getY() {
        return mTop + getTranslationY();
    }

原因如此,mTop是View 的Translation之前的位置,如果在onTouch中給View 向下Translation,那麼Translation結束之後,MotionEvent中的getY 會返回新的根據getY 計算的值,也就是說會發生突變

在java中一個線程的崩潰是不會導致進程崩潰的 setCrashDefaultHandler只能在拋出異常的時候進行最後善尾處理,並不能防止線程終止.安卓子線程或者主線程崩潰之後之所會終止 是因爲activity thread初始化的時候設置了CrashDefaultHandler 在崩潰的時候kill了進程

.addToBackStack("Mystack")
Fragment 添加返回棧之後會對生命週期產生影響

不加返回棧 FragmentTransaction replace


加返回棧 FragmentTransaction replace


加了返回棧會導致前一個被Replace 的 Fragment不走 onDestroy onDetach

同步屏障
MessageQueue#postSyncBarrier()
同步屏障會阻塞同步Message 消息 的執行,但不會阻止異步消息Message#setAsynchronous(true)執行。在同步屏障移除之後,同步消息繼續中執行(如果時間到了的話)

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