Android bug最近遇到的幾個坑解決分享
前言
6-7月的事情著實有點多。沒有清風(fēng)亦沒有鮮花,連蟬都熱到燥不動了,人自然也變得慵懶了許多。
月頭計劃和諸君探討下Compose(Compose-Desktop)和MVI實際投產(chǎn)后的感受,眼看即將到月底,低頭看著才寫一半的草稿,沉默良久,大抵這個月是沒有希望成稿了。
終究,只能挑一些最近遇到的問題寫一寫,欺騙自己還未曾擺爛。
Android R、S中的外部文件管理權(quán)限
想來是舒適太久了,目前負責(zé)的核心項目均是面向特定Android-Pad開發(fā)的,在 Android 9
已經(jīng)躺了一年,已經(jīng)快忘記了 適配 這一祖?zhèn)靼ぁ?/p>
但還有部分即將推出的項目是面向普通用戶的,順便談一談這個 老問題
在 Android 10
中提出了分區(qū)存儲,對于外部存儲空間的讀寫,除了需要處理FileProvider外,還需要配置 requestLegacyExternalStorage
。
在 Android R、S
中,進行了更嚴(yán)格的限制,需要獲取完整的外部存儲控制權(quán)限。
作者按:筆者負責(zé)的項目受技術(shù)之外因素的影響,改變現(xiàn)有的文件存儲路徑的阻力非常大
判斷與申請完全的控制權(quán)限
補充聲明權(quán)限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
那么,相關(guān)權(quán)限如下:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
并且需要運行時動態(tài)獲取權(quán)限,關(guān)鍵代碼如下:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (!Environment.isExternalStorageManager()) { //略去引導(dǎo)彈窗等相關(guān)交互邏輯 startActivityForResult(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION), REQ_CODE_FILE_MANAGE) return } } // do something
可以結(jié)合 ActivityResultContracts
改造 onActivityResult,不再贅述。
用戶對軟件授予權(quán)限后可讀寫文件。
如何快速改造
隨著Compose跨平臺技術(shù)越來越熱,將應(yīng)用的業(yè)務(wù)在多個平臺上復(fù)用也越發(fā)有價值,而平臺的差異性內(nèi)容應(yīng)當(dāng)在平臺內(nèi)部解決。
那么 如何在不修改Presenter/ViewModel 和 Model層的前提下,便捷的解決此類適配問題 越發(fā)具有價值。
當(dāng)然,此類問題的解決不能脫離實際空談,便不做具體展開。目前在項目中先運用了 Proxy
方式,對業(yè)務(wù)層追加了前置邏輯,進行了簡單處理。
我已經(jīng)設(shè)想了一個框架,并非PermissionX之類處理Android動態(tài)權(quán)限的框架,有后續(xù)實質(zhì)進展后再與諸君分享。
藍牙居然搜索不到設(shè)備
如果直接適配Android 12,大概率不會出現(xiàn)該問題(未經(jīng)過大量rom驗證),target是低版本,以往的業(yè)務(wù)代碼不到位就有可能受到影響
未適配Android 12 藍牙權(quán)限的應(yīng)用,在部分手機上發(fā)現(xiàn)未打開 "訪問我的位置信息" 時(不是定位權(quán)限?。。瑫?dǎo)致搜索不到藍牙設(shè)備。大抵是ROM廠商的杰作。
參考地圖類SDK的操作,增加以下檢測代碼:
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { //引導(dǎo)用戶開啟定位:Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) }
在 target Android12 中:
- 請求精確位置,需同時申請 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 權(quán)限
- (動態(tài))申請藍牙相關(guān)權(quán)限時,不再需要申請設(shè)備位置信息相關(guān)權(quán)限
理論上不需要再加這一檢測,但仍需經(jīng)過大量ROM測試。建議保留該段檢測邏輯。
一個高star庫中的I/O和多線程的綜合性問題
前幾日和好友閑聊時,好友提到他們的項目中使用了一個開源的下載庫 Aria
,但時不時會出現(xiàn)下載圖片失敗的問題,實質(zhì)是庫的bug。
閱讀源碼后感到問題出在細節(jié)知識上,本篇也需要水一下字數(shù),索性拿來討論一二。
庫中運用到的技術(shù):
- 多線程&鎖
- IO
- Android的Handler
其中,多線程和鎖的知識本篇不談,我亦寫有相關(guān)系列;庫作者使用Android中的Handler簡化多線程間的通信,也不再展開;IO部分系統(tǒng)展開也很長,我已有系列文章計劃,本篇結(jié)合問題簡單談一談。
和文件服務(wù)器對比,移動端的文件上傳下載并發(fā)量很小,其速度制約一般在于:服務(wù)器單連接流量限制,網(wǎng)絡(luò)條件。
對于單個文件采用多線程方式上傳、下載,只能突破 單連接流量限制,以充分使用網(wǎng)絡(luò)資源。多個文件并行上下載的產(chǎn)品意義遠大于充分吃掉網(wǎng)絡(luò)帶寬資源。
注意:上下載的高帶寬占用一定程度上會影響其他網(wǎng)絡(luò)層業(yè)務(wù)
一般對于下載而言,使用多線程時用常規(guī)的 "碎文件合并" 的思路即可。用 RandomAccessFile
并不是一個好主意。
多線程寫RandomAccessFile并非一件很美好的事情,以后的文章中細聊
在使用Java經(jīng)典IO時,使用Buffer減少IO次數(shù)可以獲得很好的性能提升,需要注意及時 flush
,該庫中用Buffer設(shè)計對 RandomAccessFile
進行了一層封裝。想來也是源自三方庫.
將部分代碼簡化后類似如下代碼:
class Demo { @Test fun mockDownload() { Looper.prepare() val appContext = InstrumentationRegistry.getInstrumentation().targetContext val file = File(appContext.cacheDir, "testtmp") if (file.exists()) { file.delete() } file.createNewFile() assertEquals(0, file.length()) val data = "hello".encodeToByteArray() val bFile = BufferedRandomAccessFile(file, "rwd", 1024) val handler = object : Handler(Looper.myLooper()) { override fun handleMessage(msg: Message?) { super.handleMessage(msg) //模擬文件合并線程,僅斷言文件大小 if (msg?.what == 1) { assertEquals(data.size.toLong(), file.length()) Looper.myLooper()?.quit() } } } //模擬下載線程 thread(priority = 1) { val ins = ByteArrayInputStream(data) try { val buffer = ByteArray(1024) var len: Int while (ins.read(buffer).also { len = it } != -1) { bFile.write(buffer, 0, len) } //模擬下載完成消息 handler.sendEmptyMessage(1) // Thread.yield() -- 模擬一下線程被切換或者因為鎖導(dǎo)致的同步 } finally { ins.close() // bFile.flush() will be invoked in close bFile.close() } } Looper.loop() } }
觀察這段代碼可以發(fā)現(xiàn),存在不安全因素:最后一個buffer塊的內(nèi)容大小未必是1024,需等到 bFile.close()
時方可寫入文件。
雖然 handler.sendEmptyMessage(1)
發(fā)送的消息會被異步執(zhí)行,但并不意味著 bFile.close()
一定會先執(zhí)行。鎖、AQS、系統(tǒng)線程調(diào)度等均可能會導(dǎo)致該問題。
以上就是Android bug最近遇到的幾個坑解決分享的詳細內(nèi)容,更多關(guān)于Android bug解決的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 動畫之TranslateAnimation應(yīng)用詳解
本節(jié)講解TranslateAnimation動畫,TranslateAnimation比較常用,比如QQ,網(wǎng)易新聞菜單條的動畫,就可以用TranslateAnimation實現(xiàn),本文將詳細介紹通過TranslateAnimation 來定義動畫,需要的朋友可以參考下2012-12-12Android ListView與ScrollView沖突的解決方法總結(jié)
這篇文章主要介紹了Android ListView與ScrollView沖突的解決方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-04-04Android-ViewModel和LiveData使用詳解
這篇文章主要介紹了Android-ViewModel和LiveData使用詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android關(guān)鍵字persistent詳細分析
這篇文章主要介紹了Android關(guān)鍵字persistent的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-04-04Android使用開源組件PagerBottomTabStrip實現(xiàn)底部菜單和頂部導(dǎo)航功能
這篇文章主要介紹了Android使用PagerBottomTabStrip實現(xiàn)底部菜單和頂部導(dǎo)航功能,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2018-08-08Android編程實現(xiàn)Home鍵的屏蔽,捕獲與修改方法
這篇文章主要介紹了Android編程實現(xiàn)Home鍵的屏蔽,捕獲與修改方法,實例分析了使用onAttachedToWindow捕獲Home鍵的相關(guān)技巧,需要的朋友可以參考下2016-06-06