Android開(kāi)發(fā)十五《綜合技術(shù)》

一米酬、使用CrashHandler來(lái)獲取Crash信息

通過(guò)設(shè)置Thread. setDefaultUncaughtExceptionHandler;

public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {}

二却妨、使用Multidex來(lái)解決方法數(shù)越界

compile 'com.android.support:multidex:1.0.2'

public class MyApplication extends MultiDexApplication { ... }

三躏惋、Android Apk編譯打包流程

編譯打包流程

編譯打包步驟:

1. 打包資源文件拄查,生成R.java文件

打包資源的工具是aapt(The Android Asset Packaing Tool(Android\sdk\build-tools\25.0.0\aapt.exe)菇怀。在這個(gè)過(guò)程中跟畅,項(xiàng)目中的AndroidManifest.xml文件和布局文件XML都會(huì)編譯咽筋,然后生成相應(yīng)的R.java,另外AndroidManifest.xml會(huì)被aapt編譯成二進(jìn)制徊件。
存放在APP的res目錄下的資源奸攻,該類資源在APP打包前大多會(huì)被編譯蒜危,變成二進(jìn)制文件,并會(huì)為每個(gè)該類文件賦予一個(gè)resource id睹耐。對(duì)于該類資源的訪問(wèn)舰褪,應(yīng)用層代碼則是通過(guò)resource id進(jìn)行訪問(wèn)的。Android應(yīng)用在編譯過(guò)程中aapt工具會(huì)對(duì)資源文件進(jìn)行編譯疏橄,并生成一個(gè)resource.arsc文件占拍,resource.arsc文件相當(dāng)于一個(gè)文件索引表,記錄了很多跟資源相關(guān)的信息捎迫。

2. 處理aidl文件晃酒,生成相應(yīng)的Java文件

這一過(guò)程中使用到的工具是aidl(Android Interface Definition Language),即Android接口描述語(yǔ)言(Android\sdk\build-tools\25.0.0\aidl.exe)窄绒。
aidl工具解析接口定義文件然后生成相應(yīng)的Java代碼接口供程序調(diào)用贝次。如果在項(xiàng)目沒(méi)有使用到aidl文件,則可以跳過(guò)這一步彰导。

3. 編譯項(xiàng)目源代碼蛔翅,生成class文件

項(xiàng)目中所有的Java代碼,包括R.java和.aidl文件位谋,都會(huì)變Java編譯器(javac)編譯成.class文件山析,生成的class文件位于工程中的bin/classes目錄下。

4. 轉(zhuǎn)換所有的class文件掏父,生成classes.dex文件

dx工具生成可供Android系統(tǒng)Dalvik虛擬機(jī)執(zhí)行的classes.dex文件笋轨,該工具位于(Android\sdk\build-tools\25.0.0\dx.bat)。
任何第三方的libraries和.class文件都會(huì)被轉(zhuǎn)換成.dex文件赊淑。dx工具的主要工作是將Java字節(jié)碼轉(zhuǎn)成成Dalvik字節(jié)碼爵政、壓縮常量池、消除冗余信息等陶缺。

5. 打包生成APK文件

所有沒(méi)有編譯的資源钾挟,如images、assets目錄下資源(該類文件是一些原始文件饱岸,APP打包時(shí)并不會(huì)對(duì)其進(jìn)行編譯掺出,而是直接打包到APP中,對(duì)于這一類資源文件的訪問(wèn)伶贰,應(yīng)用層代碼需要通過(guò)文件名對(duì)其進(jìn)行訪問(wèn))蛛砰;編譯過(guò)的資源和.dex文件都會(huì)被apkbuilder工具打包到最終的.apk文件中。
打包的工具apkbuilder位于 android-sdk/tools目錄下黍衙。apkbuilder為一個(gè)腳本文件泥畅,實(shí)際調(diào)用的是(E:\Documents\Android\sdk\tools\lib)文件中的com.android.sdklib.build.ApkbuilderMain類。

6. 對(duì)APK文件進(jìn)行簽名

一旦APK文件生成琅翻,它必須被簽名才能被安裝在設(shè)備上位仁。
在開(kāi)發(fā)過(guò)程中柑贞,主要用到的就是兩種簽名的keystore。一種是用于調(diào)試的debug.keystore聂抢,它主要用于調(diào)試钧嘶,在Eclipse或者Android Studio中直接run以后跑在手機(jī)上的就是使用的debug.keystore。
另一種就是用于發(fā)布正式版本的keystore琳疏。

7. 對(duì)簽名后的APK文件進(jìn)行對(duì)齊處理

如果你發(fā)布的apk是正式版的話有决,就必須對(duì)APK進(jìn)行對(duì)齊處理,用到的工具是zipalign(E:\Documents\Android\sdk\build-tools\25.0.0\zipalign.exe)
對(duì)齊的主要過(guò)程是將APK包中所有的資源文件距離文件起始偏移為4字節(jié)整數(shù)倍空盼,這樣通過(guò)內(nèi)存映射訪問(wèn)apk文件時(shí)的速度會(huì)更快书幕。對(duì)齊的作用就是減少運(yùn)行時(shí)內(nèi)存的使用。

四揽趾、Android Apk安裝過(guò)程

Apk安裝的主要步驟:

  1. 將apk文件復(fù)制到data/app目錄
  2. 解析apk信息
  3. Dalvik虛擬機(jī)執(zhí)行dexopt操作(優(yōu)化.dex文件成odex文件到data/dalvik-cache)台汇;如果是ART虛擬機(jī)執(zhí)行dex2oat操作(將.dex文件翻譯成.oat文件)
  4. 更新權(quán)限信息
  5. 完成安裝,發(fā)送Intent.ACTION_PACKAGE_ADDED廣播
安裝過(guò)程

五、Android的動(dòng)態(tài)加載技術(shù)

動(dòng)態(tài)加載技術(shù)也叫插件化技術(shù)篱瞎,通過(guò)插件化來(lái)減輕應(yīng)用的內(nèi)存和CPU占用苟呐。
主要解決三個(gè)基礎(chǔ)性問(wèn)題:

1、資源訪問(wèn)

ContextImpl中有兩個(gè)抽象方法:getAssets和getResources方法

2俐筋、Activity生命周期管理

將Activity生命周期方法提取出來(lái)作為一個(gè)接口牵素,通過(guò)代理Activity去調(diào)生命周期方法。

3校哎、ClassLoader的管理

同一個(gè)插件采用同一個(gè)ClassLoader加載類两波。

六、Android權(quán)限機(jī)制

Android將安全設(shè)計(jì)貫穿系統(tǒng)架構(gòu)的各個(gè)層面闷哆,覆蓋系統(tǒng)內(nèi)核、虛擬機(jī)单起、應(yīng)用程序框架層以及應(yīng)用層各個(gè)環(huán)節(jié)抱怔,力求在開(kāi)放的同時(shí),也最大程度地保護(hù)用戶的數(shù)據(jù)嘀倒、應(yīng)用程序和設(shè)備的安全屈留。Android安全模型主要提供以下幾種安全機(jī)制:


安全機(jī)制

1、權(quán)限的本質(zhì)

在 Android 中测蘑,一個(gè)權(quán)限灌危,本質(zhì)上是一個(gè)字符串,一個(gè)可以表示執(zhí)行特定操作的能力的字符串碳胳。訪問(wèn) SD 卡的能力勇蝙,訪問(wèn)通訊錄的能力,啟動(dòng)或訪問(wèn)一個(gè)第三方應(yīng)用中的組件的能力挨约。

pm list permissions -f 命令可以詳細(xì)查看 Android 所有預(yù)定義的權(quán)限

權(quán)限的信息包括:定義的包名味混、標(biāo)簽产雹、描述和保護(hù)級(jí)別

+ permission:android.permission.DELETE_PACKAGES
package:android
label:null
description:null
protectionLevel:signature|privileged

2、權(quán)限的級(jí)別

  • normal 級(jí)別:
    權(quán)限保護(hù)級(jí)別的默認(rèn)值翁锡,無(wú)須用戶確認(rèn)蔓挖,只要聲明了,就自動(dòng)默默授權(quán)馆衔。如:ACCESS_NETWORK_STATE瘟判。

  • dangerous 級(jí)別:
    賦予權(quán)限前,會(huì)彈出對(duì)話框角溃,顯式請(qǐng)求權(quán)限拷获。如:READ_SMS。因?yàn)?Android 需要在安裝時(shí)賦予權(quán)限开镣,所以安裝的確認(rèn)對(duì)話框刀诬,也會(huì)顯示列出權(quán)限清單。

  • signature 級(jí)別:
    signature 級(jí)別的權(quán)限是最嚴(yán)格的權(quán)限邪财,只會(huì)賦予與聲明權(quán)限使用相同證書(shū)的應(yīng)用程序陕壹。

以系統(tǒng)內(nèi)置 signature 級(jí)別權(quán)限為例,Android 系統(tǒng)應(yīng)用的簽名由平臺(tái)密鑰簽發(fā)树埠,默認(rèn)情況下源碼樹(shù)里有 4 個(gè)不同的密鑰文件:platform糠馆、shared、media 和 testkey怎憋。所有核心平臺(tái)的包(如:設(shè)置又碌、電話、藍(lán)牙)均使用 platform 密鑰簽發(fā)绊袋;搜索和通訊錄相關(guān)的包使用 shared 簽發(fā)毕匀;圖庫(kù)和媒體相關(guān)的包使用 media 密鑰簽發(fā);其他的應(yīng)用使用 testkey 簽發(fā)癌别。定義系統(tǒng)內(nèi)置權(quán)限的 framework-res.apk 文件是使用平臺(tái)密鑰簽發(fā)的皂岔,因此任何試圖請(qǐng)求 signature 級(jí)別內(nèi)置權(quán)限的應(yīng)用程序,需要使用與框架資源包相同的密鑰進(jìn)行簽名展姐。

  • signatureOrSystem 級(jí)別:
    可以看做是一種折中的級(jí)別躁垛,可被賦予與聲明權(quán)限具有相同簽名證書(shū)密鑰的應(yīng)用程序(同 signature 級(jí)別)或者系統(tǒng)鏡像的部分應(yīng)用,也就是說(shuō)這允許廠商無(wú)須共享簽名密鑰圾笨。Android 4.3 之前教馆,安裝在 system 分區(qū)下的應(yīng)用會(huì)被自動(dòng)賦予該保護(hù)級(jí)別的權(quán)限,而 Android 4.4 之后擂达,只允許安裝在 system/priv-app/ 目錄下的應(yīng)用才能被主動(dòng)賦予土铺。

3、權(quán)限的管理

在每個(gè)應(yīng)用安裝時(shí),權(quán)限就已經(jīng)賦予了舒憾,系統(tǒng)使用包管理服務(wù)來(lái)管理權(quán)限镀钓。打開(kāi)我們系統(tǒng)目錄下的 /data/system/packages.xml,可以看到文件包含了所有已定義的權(quán)限列表和所有 apk 的包信息镀迂,這可以看做是包管理服務(wù)維護(hù)的一個(gè)已安裝程序的核心數(shù)據(jù)庫(kù)丁溅,這個(gè)數(shù)據(jù)庫(kù),隨著每次應(yīng)用安裝探遵、升級(jí)或卸載而進(jìn)行更新窟赏。

注意:
6.0以下權(quán)限在/data/system/packages.xml
6.0以上權(quán)限在/data/system/users/0/runtime-permissions.xml

4、權(quán)限的賦予

我們知道箱季,Android 應(yīng)用安裝時(shí)涯穷,會(huì)被分配一個(gè)唯一的 UID,應(yīng)用啟動(dòng)時(shí)藏雏,包管理器會(huì)設(shè)置新建進(jìn)程的 UID 和 GID 為應(yīng)用程序的 UID拷况。如果應(yīng)用已經(jīng)被賦予了額外的權(quán)限,就把這些權(quán)限映射成一組 GID掘殴,作為補(bǔ)充 GID 分配給進(jìn)程赚瘦。低層就可以依賴于進(jìn)程的 UID、GID 和補(bǔ)充 GID 來(lái)決定是否賦予權(quán)限了奏寨。

內(nèi)置權(quán)限到 GID 的映射是定義在 /etc/permission/platform.xml 起意;
6.0以上動(dòng)態(tài)添加到以上位置
系統(tǒng)進(jìn)程的權(quán)限配置信息在
Android\system\core\include\private\android_filesystem_config.h

注意:
PID:表示應(yīng)用的進(jìn)程 ID,PPID 表示父進(jìn)程 ID病瞳;
UID:UID代表一個(gè)應(yīng)用揽咕;應(yīng)用安裝時(shí)分配一個(gè)唯一的;

5套菜、權(quán)限的檢查

1. 系統(tǒng)內(nèi)核層權(quán)限檢查

內(nèi)核代碼中current_has_network(void) 方法檢查了進(jìn)程的所在組亲善。如果不在 inet 組,則直接返回錯(cuò)誤逗柴。設(shè)置申請(qǐng)權(quán)限逗爹,經(jīng)過(guò)解析,逐步映射到內(nèi)核層的組 ID 和用戶 ID嚎于,最終才能通過(guò)內(nèi)核層的檢查。

2. 框架層權(quán)限檢查

Android 6.0 之前組件不能在運(yùn)行時(shí)改變權(quán)限挟冠,所以系統(tǒng)的權(quán)限檢查執(zhí)行過(guò)程是靜態(tài)的

  • 動(dòng)態(tài)權(quán)限執(zhí)行:
    通過(guò)IPC:Android 的核心系統(tǒng)服務(wù)統(tǒng)一會(huì)注冊(cè)到服務(wù)管理器于购,系統(tǒng)服務(wù)可以直接檢查調(diào)用者的 UID,通過(guò)限定 UID 來(lái)控制訪問(wèn)權(quán)限知染;不適合非固定UID的應(yīng)用肋僧,適合只允許以 root(UID:0) 或 system(UID:1000) 運(yùn)行的進(jìn)程訪問(wèn)的服務(wù)檢查。
  • 靜態(tài)權(quán)限執(zhí)行
    跨應(yīng)用組件交互
    我們使用隱式 Intent 來(lái)表達(dá)意圖,搜索匹配的組件嫌吠,如果有多個(gè)止潘,彈出選擇框,目標(biāo)組件被選定后辫诅,會(huì)由 ActivityManagerService 執(zhí)行權(quán)限檢查凭戴,檢查目標(biāo)組件是否有相應(yīng)的權(quán)限要求,如果有炕矮,則把權(quán)限檢查的工作交給 PMS么夫,去檢查調(diào)用者有沒(méi)有被授權(quán)這些權(quán)限。
    接下來(lái)的總體的流程和動(dòng)態(tài)執(zhí)行流程大致相同:Binder.getCallingUid()和Binder.getCallingPid()獲取調(diào)用者的 UID 和 PID肤视,然后利用 UID 映射包名档痪,再獲得相關(guān)權(quán)限集合。如果權(quán)限集合中含有所需權(quán)限即啟動(dòng)邢滑,否則拋出 SecurityException 異常腐螟。靜態(tài)權(quán)限執(zhí)行這里,我們可以詳細(xì)了解下困后,每種組件的權(quán)限檢查時(shí)機(jī)和具體順序是怎么樣的乐纸。
  • 組件權(quán)限執(zhí)行
Activity

會(huì)在 startActivity() 和 startActivityForResult() 里解析到聲明權(quán)限的 Activity 時(shí),就執(zhí)行權(quán)限檢查操灿。

Service

startService()锯仪、stopService() 和 bindService(),這 3 個(gè)方法被調(diào)用時(shí)都會(huì)進(jìn)行權(quán)限檢查趾盐。

BroadCastReceiver

發(fā)送廣播除了常用的 sendBroadcast(Intent intent)庶喜,還有個(gè) sendBroadcast(Intent intent, String receiverPermission),該方法可以要求廣播接受者具備特定的權(quán)限救鲤,但是久窟,調(diào)用 sendBroadcast 是不會(huì)進(jìn)行權(quán)限檢查的,因?yàn)閺V播是異步的本缠,所以權(quán)限檢查會(huì)在 intent 傳遞到已注冊(cè)的廣播接受者時(shí)進(jìn)行斥扛,如果接收者不具備特定的權(quán)限,則不會(huì)接收到該廣播丹锹,也不會(huì)收到 SecurityException 異常稀颁。

反過(guò)來(lái),接收者可以要求廣播發(fā)送者必須具備的權(quán)限楣黍,所要求的權(quán)限在 manifest 文件中設(shè)置 <receiver> 標(biāo)簽的 permission 屬性匾灶,或者動(dòng)態(tài)注冊(cè)時(shí)指定 registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler),權(quán)限檢查也是在廣播傳遞時(shí)執(zhí)行租漂。

所以阶女,收發(fā)廣播可以分開(kāi)指定權(quán)限颊糜。值得一提的是,一些系統(tǒng)廣播被聲明為 protected秃踩,并且只能由系統(tǒng)進(jìn)程發(fā)送衬鱼,比如 PACKAGE_INSTALLED。只能由系統(tǒng)進(jìn)程發(fā)送憔杨,這個(gè)限制會(huì)在內(nèi)核層進(jìn)行檢查鸟赫,對(duì)調(diào)用者的 UID 進(jìn)行匹配,只能是 SYSTEM_UID芍秆、PHONE_UID惯疙、SHELL_UID、BLUETOOTH_UID 或 root妖啥。如果其他 UID 的進(jìn)程試圖發(fā)送系統(tǒng)廣播霉颠,則會(huì)收到 SecurityException 異常。

ContentProvider

ContentProvider 可以為讀寫(xiě)分別指定不同的權(quán)限荆虱,即:調(diào)用目標(biāo) provider蒿偎、query() 方法 和 insert()、update()怀读、delete() 都會(huì)進(jìn)行權(quán)限檢查诉位。

總結(jié):
Android 的權(quán)限的檢查會(huì)在各個(gè)層次上實(shí)施。
1菜枷、高層的組件苍糠,例如應(yīng)用和系統(tǒng)服務(wù),通過(guò)包管理器查詢應(yīng)用程序被賦予的權(quán)限啤誊,并決定是否準(zhǔn)予訪問(wèn)岳瞭。
2、低層的組件蚊锹,通常不訪問(wèn)包管理器瞳筏,比如本地守護(hù)進(jìn)程,依賴于進(jìn)程的 UID牡昆、GID 和補(bǔ)充 GID 來(lái)決定賦予姚炕。
3、訪問(wèn)系統(tǒng)資源時(shí)丢烘,如設(shè)備文件柱宦、UNIX 域套接字和網(wǎng)絡(luò)套接字,則由內(nèi)核根據(jù)所有者播瞳、目標(biāo)資源的訪問(wèn)權(quán)限和訪問(wèn)進(jìn)程的進(jìn)程屬性或者 packages.list 來(lái)進(jìn)行控制捷沸。

共享 UID
最后簡(jiǎn)單說(shuō)下共享 UID,填一下前面挖的坑狐史。雖說(shuō) Android 會(huì)為每一個(gè)應(yīng)用分配唯一的 UID痒给,但如果應(yīng)用使用相同的密鑰簽發(fā),就可以使用相同 UID 運(yùn)行骏全,也就是運(yùn)行在同一個(gè)進(jìn)程中苍柏。
這個(gè)特性被系統(tǒng)應(yīng)用和核心框架服務(wù)廣泛使用,比如:Google Play 和 Google 定位服務(wù)姜贡,請(qǐng)求同一進(jìn)程內(nèi)的 Google 登錄服務(wù)试吁,從而達(dá)到靜默自動(dòng)同步用戶數(shù)據(jù)的體驗(yàn)。
值得注意的是:Android 不支持將一個(gè)已安裝的應(yīng)用楼咳,從非共享 UID 切換到共享狀態(tài)熄捍,因?yàn)楦淖兞艘寻惭b應(yīng)用的 UID,會(huì)導(dǎo)致應(yīng)用失去對(duì)自己文件的訪問(wèn)權(quán)限(在一些早期 Android 版本中)母怜,所以如果使用共享 UID 必須從一開(kāi)始就設(shè)計(jì)好余耽。

參考:你真的了解Android權(quán)限機(jī)制嗎

七、Android劉海屏適配

1苹熏、AndroidP劉海屏的適配:

Android P 支持最新的全面屏以及為攝像頭和揚(yáng)聲器預(yù)留空間的凹口屏幕碟贾。通過(guò)全新的 DisplayCutout 類,可以確定非功能區(qū)域的位置和形狀轨域,這些區(qū)域不應(yīng)顯示內(nèi)容袱耽。要確定這些凹口屏幕區(qū)域是否存在及其位置,使用 getDisplayCutout() 函數(shù)干发。

  1. 設(shè)置LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES模式
  2. 設(shè)置沉浸式布局模式
  3. 計(jì)算狀態(tài)欄高度朱巨,進(jìn)行布局;如果有特殊UI要求枉长,則可以使用DisplayCutoutDemo類去獲取劉海屏的坐標(biāo)冀续,完成UIAndroid P 中 WindowManager.LayoutParams 新增了一個(gè)布局參數(shù)屬性layoutInDisplayCutoutMode:
DisplayCutout 類方法 說(shuō)明
getBoundingRects() 返回Rects的列表,每個(gè)Rects都是顯示屏上非功能區(qū)域的邊界矩形
getSafeInsetLeft () 返回安全區(qū)域距離屏幕左邊的距離,單位是px
getSafeInsetRight () 返回安全區(qū)域距離屏幕右邊的距離,單位是px
getSafeInsetTop () 返回安全區(qū)域距離屏幕頂部的距離嗅绸,單位是px
getSafeInsetBottom() 返回安全區(qū)域距離屏幕底部的距離掌实,單位是px
View decorView = mAc.getWindow().getDecorView();
        if(decorView != null){
            Log.d("hwj", "**controlView**" + android.os.Build.VERSION.SDK_INT);
            Log.d("hwj", "**controlView**" + android.os.Build.VERSION_CODES.P);
            WindowInsets windowInsets = decorView.getRootWindowInsets();
            if(windowInsets != null){
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
                    DisplayCutout displayCutout = windowInsets.getDisplayCutout();
                    //getBoundingRects返回List<Rect>,沒(méi)一個(gè)list表示一個(gè)不可顯示的區(qū)域,即劉海屏该面,可以遍歷這個(gè)list中的Rect,
                    //即可以獲得每一個(gè)劉海屏的坐標(biāo)位置,當(dāng)然你也可以用類似getSafeInsetBottom的api
                    Log.d("hwj", "**controlView**" + displayCutout.getBoundingRects());
                    Log.d("hwj", "**controlView**" + displayCutout.getSafeInsetBottom());
                    Log.d("hwj", "**controlView**" + displayCutout.getSafeInsetLeft());
                    Log.d("hwj", "**controlView**" + displayCutout.getSafeInsetRight());
                    Log.d("hwj", "**controlView**" + displayCutout.getSafeInsetTop());
                }
            }
        }

Android P 中 WindowManager.LayoutParams 新增了一個(gè)布局參數(shù)屬性 layoutInDisplayCutoutMode:

模式 模式說(shuō)明
LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT 只有當(dāng)DisplayCutout完全包含在系統(tǒng)欄中時(shí),才允許窗口延伸到DisplayCutout區(qū)域。 否則功炮,窗口布局不與DisplayCutout區(qū)域重疊。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 該窗口決不允許與DisplayCutout區(qū)域重疊术唬。
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 該窗口始終允許延伸到屏幕短邊上的DisplayCutout區(qū)域薪伏。Android P 之前的劉海屏適配

2、AndroidP之前劉海屏的適配

不同廠商的劉海屏適配方案不盡相同粗仓,需分別查閱各自的開(kāi)發(fā)者文檔嫁怀。

3设捐、劉海屏判斷

/**
 * 判斷是否是劉海屏
 * @return
 */
public static boolean hasNotchScreen(Activity activity){
    if (getInt("ro.miui.notch",activity) == 1 || hasNotchAtHuawei(activity) || hasNotchAtOPPO()
            || hasNotchAtVivo(activity) || isAndroidP(activity) != null){ //TODO 各種品牌
        return true;
    }
 
    return false;
}
 
/**
 * Android P 劉海屏判斷
 * @param activity
 * @return
 */
public static DisplayCutout isAndroidP(Activity activity){
    View decorView = activity.getWindow().getDecorView();
    if (decorView != null && android.os.Build.VERSION.SDK_INT >= 28){
        WindowInsets windowInsets = decorView.getRootWindowInsets();
        if (windowInsets != null)
            return windowInsets.getDisplayCutout();
    }
    return null;
}
 
/**
 * 小米劉海屏判斷.
 * @return 0 if it is not notch ; return 1 means notch
 * @throws IllegalArgumentException if the key exceeds 32 characters
 */
public static int getInt(String key,Activity activity) {
    int result = 0;
    if (isXiaomi()){
        try {
            ClassLoader classLoader = activity.getClassLoader();
            @SuppressWarnings("rawtypes")
            Class SystemProperties = classLoader.loadClass("android.os.SystemProperties");
            //參數(shù)類型
            @SuppressWarnings("rawtypes")
            Class[] paramTypes = new Class[2];
            paramTypes[0] = String.class;
            paramTypes[1] = int.class;
            Method getInt = SystemProperties.getMethod("getInt", paramTypes);
            //參數(shù)
            Object[] params = new Object[2];
            params[0] = new String(key);
            params[1] = new Integer(0);
            result = (Integer) getInt.invoke(SystemProperties, params);
 
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    return result;
}
 
/**
 * 華為劉海屏判斷
 * @return
 */
public static boolean hasNotchAtHuawei(Context context) {
    boolean ret = false;
    try {
        ClassLoader classLoader = context.getClassLoader();
        Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
        Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
        ret = (boolean) get.invoke(HwNotchSizeUtil);
    } catch (ClassNotFoundException e) {
        AppLog.e("hasNotchAtHuawei ClassNotFoundException");
    } catch (NoSuchMethodException e) {
        AppLog.e("hasNotchAtHuawei NoSuchMethodException");
    } catch (Exception e) {
        AppLog.e( "hasNotchAtHuawei Exception");
    } finally {
        return ret;
    }
}
 
public static final int VIVO_NOTCH = 0x00000020;//是否有劉海
public static final int VIVO_FILLET = 0x00000008;//是否有圓角
 
/**
 * VIVO劉海屏判斷
 * @return
 */
public static boolean hasNotchAtVivo(Context context) {
    boolean ret = false;
    try {
        ClassLoader classLoader = context.getClassLoader();
        Class FtFeature = classLoader.loadClass("android.util.FtFeature");
        Method method = FtFeature.getMethod("isFeatureSupport", int.class);
        ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);
    } catch (ClassNotFoundException e) {
        AppLog.e( "hasNotchAtVivo ClassNotFoundException");
    } catch (NoSuchMethodException e) {
        AppLog.e(  "hasNotchAtVivo NoSuchMethodException");
    } catch (Exception e) {
        AppLog.e(  "hasNotchAtVivo Exception");
    } finally {
        return ret;
    }
}
/**
 * OPPO劉海屏判斷
 * @return
 */
public static boolean hasNotchAtOPPO(Context context) {
    return  context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市塘淑,隨后出現(xiàn)的幾起案子萝招,更是在濱河造成了極大的恐慌,老刑警劉巖存捺,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件槐沼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡捌治,警方通過(guò)查閱死者的電腦和手機(jī)岗钩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)肖油,“玉大人兼吓,你說(shuō)我怎么就攤上這事」乖希” “怎么了周蹭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)疲恢。 經(jīng)常有香客問(wèn)我凶朗,道長(zhǎng),這世上最難降的妖魔是什么显拳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任棚愤,我火速辦了婚禮,結(jié)果婚禮上杂数,老公的妹妹穿的比我還像新娘宛畦。我一直安慰自己,他們只是感情好揍移,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布次和。 她就那樣靜靜地躺著,像睡著了一般那伐。 火紅的嫁衣襯著肌膚如雪踏施。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,610評(píng)論 1 305
  • 那天罕邀,我揣著相機(jī)與錄音畅形,去河邊找鬼。 笑死诉探,一個(gè)胖子當(dāng)著我的面吹牛日熬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肾胯,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼竖席,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼耘纱!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起怕敬,我...
    開(kāi)封第一講書(shū)人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤揣炕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后东跪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鹰溜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年虽填,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片曹动。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡斋日,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出墓陈,到底是詐尸還是另有隱情恶守,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布贡必,位于F島的核電站兔港,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏仔拟。R本人自食惡果不足惜衫樊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望利花。 院中可真熱鬧科侈,春花似錦、人聲如沸炒事。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)挠乳。三九已至权薯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間欲侮,已是汗流浹背崭闲。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留威蕉,地道東北人刁俭。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像韧涨,于是被迫代替她去往敵國(guó)和親牍戚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子侮繁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容