1. 程式人生 > 其它 >零碎知識點記錄

零碎知識點記錄

注:本部落格不定期更新

  1. transient使用小結

    1. 一旦變數被 transient 修飾,變數將不再是物件持久化的一部分,該變數內容在序列化後無法獲得訪問
    2. transient 關鍵字只能修飾變數,而不能修飾方法和類。注意,區域性變數(又叫本地變數,如方法內的變數)是不能被 transient 關鍵字修飾的。變數如果是使用者自定義的類變數,則該類需要實現 Serializable 介面
    3. 被 transient 關鍵字修飾的變數不再能被序列化,一個靜態變數不管是否被 transient 修飾,均不能被序列化
  2. 在 JAVA 中,被 synchronized 關鍵字修飾的 程式碼塊 或 方法塊,在同一時刻只允許 一個執行緒執行。當有執行緒獲取該記憶體鎖後,其它執行緒無法訪問該記憶體,從而實現 JAVA 中簡單的同步、明白這個原理,就能理解 synchronized(this)、synchronized(Object) 和 synchronized(T.class) 的區別了。

    1. synchronized(this) 鎖住的是 this 所指代的物件,如果已有執行緒獲取的 this 指代物件的記憶體鎖,其它任何訪問該物件的執行緒都會被阻塞,直到同步程式碼塊執行完成。
    2. synchronized(Object) 鎖住的物件是 Object 物件,這樣當某執行緒正在執行同步程式碼塊時,其它執行緒只是會被 Object 阻塞,但是仍然可能訪問 this 指代的物件的其它方法。
    3. synchronized(T.class) 鎖住的是 T 類的所有例項物件, 只要有任意一個執行緒在訪問 T 類, 其它執行緒都會被阻塞住。這種方式效率比較低,不建議使用。
  3. Object.wait(),與 Object.notify() 必須要與 synchronized(Obj) 一起使用,也就是 wait,與 notify 是針對已經獲取了 Object 鎖進行操作,從語法角度來說就是 Object.wait(), Object.notify() 必須在 synchronized(Obj){...} 語句塊內。從功能上來說 wait 就是 A 執行緒在獲取到物件 Object 的物件鎖後(wait 在 synchronized 程式碼塊內,進來就表示已經獲取到物件鎖),主動釋放物件鎖,同時 A 執行緒休眠。直到有其它執行緒 B 呼叫物件 Object 的 notify() 喚醒 A 執行緒,才能繼續獲取物件鎖,並繼續執行。notify() 會使 B 執行緒失去物件鎖。有一點需要注意的是 notify() 呼叫後,並不是馬上就釋放物件鎖的,而是在相應的 synchronized() 程式碼塊執行結束,自動釋放鎖後,JVM 會在 wait() Object 的物件鎖的執行緒中隨機選取一執行緒,賦予其物件鎖,喚醒執行緒,繼續執行。Thread.sleep() 與 Object.wait() 二者都可以暫停當前執行緒,釋放 CPU 控制權,主要的區別在於 Object.wait() 在釋放 CPU 同時,釋放了物件鎖的控制。

  4. 分享功能(如分享圖片到 QQ)如果不知道具體的頁面,可以用以下方法可以確定具體的Activity。其核心程式碼如下:

    Intent shareIntent = new Intent();
    // Intent 意為意圖,表示將要做的事,Action 設定為 SEND,則表示想要進行傳送
    shareIntent.setAction(Intent.ACTION_SEND);
    // 設定 MIME type,表示將要傳送的資料型別。
    shareIntent.setTypeAndNormalize("image/*");
    
    PackageManager packageManager = getPackageManager();
    // 查詢目標資訊
    List<ResolveInfo> tempList = packageManager.queryIntentActivities(shareIntent, PackageManager.GET_META_DATA);
    

    要得到所有的包名與類名,

    首先我們可以定義一個數據結構:

    public class AppInfo {
        // 包名
        private String packageName;
        // activity 名
        private String activityName;
    }
    

    然後,通過 Context 拿到 PackageManager:

    PackageManager packageManager = context.getPackageManager();
    

    第 3 步,我們定義一個 Intent,Intent 意為意圖,表示將要做的事。我們將動作設定為 SEND,資料型別設定為圖片。表示告訴系統,我們想要傳送型別為圖片的東西。

    Intent shareIntent = new Intent();
    shareIntent.setAction(Intent.ACTION_SEND);
    shareIntent.setTypeAndNormalize("image/*");
    

    Android 中使用的 MIME Type 是 RFC 定義的標準格式,是大小寫敏感的。setTypeAndNormalize 可以在 setType的基礎上,將使用者的異常輸入抹消(通通變為小寫)。所有的圖片型別,可以用 * 指代。image/*泛指圖片,不涉及具體的圖片型別。

    第 4 步,根據 Intent 篩選出我們想要的資訊。

    private Vector<AppInfo> getAppInfoList() {
        Intent shareIntent = new Intent();
        shareIntent.setAction(Intent.ACTION_SEND);
        shareIntent.setTypeAndNormalize("image/*");
    
        PackageManager packageManager = getPackageManager();
    
        // 根據上面的設定,篩選出的結果
        List<ResolveInfo> tempList = packageManager.queryIntentActivities(shareIntent, PackageManager.GET_META_DATA);
        // 將所有的資料打印出來,方便我們人為辨別
        Log.d(TAG, "查詢到的列表: " + tempList);
        if(tempList.size() <= 0) {
            return new Vector<>(0);
        }
        Vector<AppInfo> appInfoList = new Vector<>();
        AppInfo appInfo;
        for(ResolveInfo info : tempList) {
            if(info == null) {
                continue;
            }
            // 將資料篩入我們自定義的資料結構中
            appInfo = new AppInfo();
            appInfo.setPackageName(info.activityInfo.packageName);
            appInfo.setActivityName(info.activityInfo.name);
            appInfoList.add(appInfo);
        }
    
        return appInfoList;
    }
    

    下面以篩選 QQ 為例,首先,我們拿到了所有的包名、類名:

    [
        ResolveInfo{728890e com.android.bluetooth/.opp.BluetoothOppLauncherActivity m=0x608000}, 
        ResolveInfo{4d8cf2f com.android.mms/.ui.ComposeMessageRouterActivity m=0x608000}, 
        ResolveInfo{72956c5 cn.wps.moffice_eng/cn.wps.moffice.main.scan.ui.ThirdpartyImageToPdfActivity m=0x608000}, 
        ResolveInfo{668281a cn.wps.moffice_eng/cn.wps.moffice.main.scan.ui.ThirdpartyImageToTextActivity m=0x608000}, 
        ResolveInfo{4ad2f4b cn.wps.moffice_eng/cn.wps.moffice.main.scan.ui.ThirdpartyImageToXlsActivity m=0x608000}, 
        ResolveInfo{1282d28 cn.wps.moffice_eng/cn.wps.moffice.main.scan.ui.ThirdpartyImageToPptActivity m=0x608000}, 
        ResolveInfo{cd9a41 cn.wps.moffice_eng/cn.wps.moffice.main.cloud.drive.upload.UploadFileActivity m=0x608000}, 
        ResolveInfo{bd0abe6 com.android.email/com.kingsoft.mail.compose.ComposeActivity m=0x608000}, 
        ResolveInfo{e67b35 com.qiyi.video/org.qiyi.android.video.MainActivity m=0x608000}, 
        ResolveInfo{baacbca com.qiyi.video/com.qiyi.scan.ARWrapperActivity m=0x608000}, 
        ResolveInfo{a0c813b com.quark.browser/com.ucpro.MainActivity m=0x608000}, 
        ResolveInfo{5553058 com.sina.weibo/.composerinde.ComposerDispatchActivity m=0x608000}, 
        ResolveInfo{61095b1 com.sina.weibo/.story.publisher.StoryDispatcher m=0x608000}, 
        ResolveInfo{a91da96 com.sina.weibo/.weiyou.share.WeiyouShareDispatcher m=0x608000}, 
        ResolveInfo{6b7f617 com.taobao.taobao/com.etao.feimagesearch.IrpActivity m=0x608000}, 
        ResolveInfo{618fa04 com.tencent.androidqqmail/com.tencent.qqmail.launcher.third.LaunchComposeNote m=0x608000}, 
        ResolveInfo{7e7dbed com.tencent.androidqqmail/com.tencent.qqmail.launcher.third.LaunchComposeMail m=0x608000}, 
        ResolveInfo{9090a22 com.tencent.androidqqmail/com.tencent.qqmail.launcher.third.LaunchFtnUpload m=0x608000}, 
        ResolveInfo{23bdcb3 com.tencent.mm/.ui.tools.ShareImgUI m=0x608000}, 
        ResolveInfo{62db270 com.tencent.mm/.ui.tools.AddFavoriteUI m=0x608000},
        ResolveInfo{7349e9 com.tencent.mm/.ui.tools.ShareToTimeLineUI m=0x608000}, 
        ResolveInfo{4d1a66e com.tencent.mobileqq/.activity.JumpActivity m=0x608000}, 
        ResolveInfo{50d910f com.tencent.mobileqq/.activity.qfileJumpActivity m=0x608000}, 
        ResolveInfo{993859c com.tencent.mobileqq/cooperation.qlink.QlinkShareJumpActivity m=0x608000}, 
        ResolveInfo{92d9ba5 com.tencent.mobileqq/cooperation.qqfav.widget.QfavJumpActivity m=0x608000}, 
        ResolveInfo{475bb7a com.tencent.mtt/.businesscenter.intent.IntentDispatcherActivity m=0x608000}, 
        ResolveInfo{f9c2f2b com.tencent.mtt/.external.reader.thirdcall.ThirdCallDispatchActivity m=0x608000}, 
        ResolveInfo{2215f88 com.tencent.mtt/.external.imageedit.ImageEditActivity m=0x608000}, 
        ResolveInfo{e814d21 com.tencent.wework/.launch.AppSchemeLaunchActivity m=0x608000}, 
        ResolveInfo{b141546 com.tmall.wireless/.splash.TMSplashActivity m=0x608000}, 
        ResolveInfo{2c9307 com.xiaomi.scanner/.app.ScanActivity m=0x608000}, 
        ResolveInfo{630ec34 net.windcloud.explorer/.FileExplorerTabActivity m=0x608000}
    ]
    

    找到了目標的幾個頁面:

    ResolveInfo{4d1a66e com.tencent.mobileqq/.activity.JumpActivity m=0x608000}, 
    ResolveInfo{50d910f com.tencent.mobileqq/.activity.qfileJumpActivity m=0x608000}, 
    ResolveInfo{993859c com.tencent.mobileqq/cooperation.qlink.QlinkShareJumpActivity m=0x608000}, 
    ResolveInfo{92d9ba5 com.tencent.mobileqq/cooperation.qqfav.widget.QfavJumpActivity m=0x608000}, 
    

    此時仍然不知道是哪個頁面,我們需要去網上查資料。大功告成,得到了最終的 Activity:

    // 包名:com.tencent.mobileqq
    // 類名:com.tencent.mobileqq.activity.JumpActivity
    

    值得注意的是,SetAction 為 SEND 後的查詢,得到的是一個列表,如果我們不用查詢Activity,而是直接用 chooseDialog 開啟,就會出現下面這種選擇框,表示系統要我們選擇哪個應用。這些應用其實也就是系統查詢出來,然後展示的。

    上面要我們選擇,是因為我們並沒有指定具體的應用,具體的類。用的也是通用的動作(Action),而不是私有的動作(Action)。

  5. 下面的程式碼也有可能報空指標:

    public class Bean {
        Long id;
        
        public Long getId() {
            return id;
        }
    }
    
    public boolean isOne(Bean bean) {
        if(bean == null) {
            return false;
        }
        return bean.getId() == 1L;
    }
    

    原因就是bean.getId()拿到的是一個 Long(裝箱後的資料),而 1L 是一個基本型別 long。如果用==的形式,則 Long 需要先拆箱,拆箱過程中,如果 id 為空,也會報空指標。所以仍然需要加上bean.getId() == null的判斷。

  6. 聲音分貝值的計算:dB = 20 * lg(幅度 / 0.00002),0.00002 是 20 微帕,通常被人為是人能聽到的最小聲音。原始公式:1dB = 20 * log(A1 / A2),其中 A1 和 A2 是兩個聲音的振幅。取樣大小為 8bit 也就是 1 個位元組時,最大振幅是最小振幅的 256 倍。因此,動態範圍是 48 分貝,計算公式如下:dB = 20 * log(256)。48 分貝的動態範圍大約是一個安靜房間和一臺執行著電動割草機之間的區別。如果將聲音取樣大小增加一倍到 16bit,產生的動態範圍則為 96 分貝,計算公式如下:dB = 20 * log(65536)。這非常接近聽力最低閾值和產生痛感之間的區別,這個範圍被認為非常適合還原音樂。

  7. 倉庫使用阿里雲

    buildscript {
        //阿里雲映象
        repositories {
            maven{ url 'http://maven.aliyun.com/nexus/content/repositories/central/'}
            maven{ url'http://maven.aliyun.com/nexus/content/repositories/jcenter'}
            google()
            //jcenter()
        }
    }
    
    allprojects {
        //阿里雲映象
        repositories {
            maven{ url 'http://maven.aliyun.com/nexus/content/repositories/central/'}
            maven{ url'http://maven.aliyun.com/nexus/content/repositories/jcenter'}
            google()
            //jcenter()
        }
    }
    
  8. 音視訊大神:雷霄曄,閆令琪

  9. SpannableString + ImageSpan 可以在 TextView 實現富文字的效果

  10. 位元組碼增強技術:ASM。ASM 是一個 java 位元組碼操縱框架。

  11. CountDownLatch 可以讓非同步操作變成同步操作。

  12. Scroller 的程式碼模版:

/**
 * 一般的滑動是瞬時的,可以通過 Scroller 進行平滑
 * 
 * 下面的是 Scroller 的程式碼模版
 */
public class TestView extends View {
    Scroller scroller;

    public TestView(Context context) {
        super(context);
        scroller = new Scroller(getContext());
    }

    @Override
    public void computeScroll() {
        if (scroller.computeScrollOffset()) {
            scrollTo(scroller.getCurrX(), scroller.getCurrY());
            postInvalidate();
        }
    }

    // 緩慢滾動到指定位置
    private void smoothScrollTo(int destX, int destY) {
        int scrollX = getScrollX();
        int delta = destX - scrollX;
        // 1000ms內滑向destX,效果就是慢慢滑動
        scroller.startScroll(scrollX, 0, delta, 0, 1000);
        invalidate();
    }
}
  1. 負數以其正值的補碼錶示(補碼 = 反碼 + 1),負數的無符號右移,高位補0,出來的數會很大
  2. 每個 Activity、Dialog、Toast、PopUpWindow 都對應了一個 window。
  3. 渲染部分的效能優化
  • 渲染操作通常依賴於兩個核心元件:CPU 與 GPU。CPU 負責包括 Measure,Layout,Record,Execute 的計算操作,GPU 負責 Rasterization(柵格化)操作。CPU 通常存在的問題的原因是存在非必需的檢視元件,它不僅僅會帶來重複的計算操作,而且還會佔用額外的 GPU 資源。
  • CPU 負責把 UI 元件計算成 Polygons,Texture 紋理,然後交給 GPU 進行柵格化渲染。每次從CPU轉移到GPU是一件很麻煩的事情。
  • 解決過度繪製(某個畫素在同一幀的時間內被繪製了多次)的方法為:
    • 移除 Window 預設的 Background
    • 移除 XML 佈局檔案中非必需的 Background
    • 按需顯示佔位背景圖片(比如 imageView 拿到 drawable 就不顯示 background 了,拿不到才顯示)
    • 自定義 view 時,使用 canvas.clipRect() 來幫助系統識別那些可見的區域。這個方法可以指定一塊矩形區域,只有在這個區域內才會被繪製,其他的區域會被忽視。這個 API 可以很好的幫助那些有多組重疊元件的自定義 View 來控制顯示的區域。同時 clipRect 方法還可以幫助節約 CPU 與 GPU 資源,在 clipRect 區域之外的繪製指令都不會被執行,那些部分內容在矩形區域內的元件,仍然會得到繪製。
    • 自定義 view 時,還可以使用 canvas.quickreject() 來判斷是否沒和某個矩形相交,從而跳過那些非矩形區域內的繪製操作。
    • Android 需要把 XML 佈局檔案轉換成 GPU 能夠識別並繪製的物件。這個操作是在 DisplayList 的幫助下完成的。DisplayList 持有所有將要交給 GPU 繪製到螢幕上的資料資訊。在某個 View 第一次需要被渲染時,Display List 會因此被建立,當這個 View 要顯示到螢幕上時,我們會執行 GPU 的繪製指令來進行渲染。如果 View 的 Property 屬性發生了改變(例如移動位置),我們就僅僅需要 Execute Display List 就夠了。然而如果你修改了 View中 的某些可見元件的內容,那麼之前的 DisplayList 就無法繼續使用了,我們需要重新建立一個 DisplayList 並重新執行渲染指令更新到螢幕上。
    • 任何時候 View 中的繪製內容發生變化時,都會需要重新建立 DisplayList,渲染 DisplayList,更新到螢幕上等一系列操作。這個流程的表現效能取決於你的 View 的複雜程度,View 的狀態變化以及渲染管道的執行效能。舉個例子,假設某個 Button 的大小需要增大到目前的兩倍,在增大 Button 大小之前,需要通過父 View 重新計算並擺放其他子 View 的位置。修改 View 的大小會觸發整個 HierarcyView 的重新計算大小的操作。如果是修改 View 的位置則會觸發 HierarchView 重新計算其他 View 的位置。如果佈局很複雜,這就會很容易導致嚴重的效能問題。
    • 提升佈局效能的關鍵點是儘量保持佈局層級的扁平化,避免出現重複的巢狀佈局。RelativeLayout 在 measure 這一步耗時賊嚴重。是因為相對佈局需要給所有子 View 水平方向測量一次,再豎直方向測量一次,才能確定每個子 View 的大小。層級一旦太深,measure 時間以指數上升。LinearLayout 如果子 View 的 LayoutParams 裡有使用 weight 屬性的話,measure 時間和 RelativeLayout 幾乎接近,因為也需要給每個子 View 測量兩次。儘量少寫層級深的佈局,能減少一個檢視節點就少一些 measure 時間
  1. 寫 gradle 時,如果想要新增上程式碼提示和 API 說明,可以使用以下程式碼:
// 根專案的 build.gradle 檔案中加入以下程式碼
buildscript {
    repositories {
        maven {
            url "https://repo.gradle.org/gradle/libs-releases-local"
        }
    }
    dependencies {
        // 引入 Android 外掛,具體版本號見 maven 倉庫
        classpath 'com.android.tools.build:gradle:${version}'
        // 引入 Android 外掛 API
        classpath "com.android.tools.build:gradle-api:${version}"
        // classpath "com.android.tools.build:gradle-core:${version}"
        // 引入 Groovy
        // classpath "org.codehaus.groovy:groovy-all:${version}"
        // version 為 gradle-core-api 的版本,如:6.1.1
        classpath "org.gradle:gradle-core-api:${version}"
    }
}
  1. 根專案下新建 buildSrc 資料夾,當做一個新的 module(具備 src/main/java 目錄和 build.gradle 檔案),這個 module 預設會被根專案依賴。在其他模組和根專案的 build.gradle 檔案中,可以使用 buildSrc 模組中定義的類。
  2. gradle 包的快取目錄:C:\Users\使用者名稱.gradle\wrapper\dists\gradle版本號\wrapper\dists; Android gradle plugin 包的快取目錄:C:\Users\使用者名稱.gradle\wrapper\dists\gradle版本號\caches\modules-2\files-2.1\com.android.tools.build\gradle。gradle 和 Android 外掛具體是哪個版本,以 build.gradle 中的宣告為準。
  3. RecyclerView 的使用包含以下幾個方面:
    1. Adapter+ViewHolder
    2. ItemDecoration
    3. ItemAnimator
    4. LayoutManager
    5. ItemTouchHelper
    6. SnapHelper
    7. Cache
    8. notify and refresh