1. 程式人生 > >Android機型適配之痛

Android機型適配之痛

CSDN移動將持續為您優選移動開發的精華內容,共同探討移動開發的技術熱點話題,涵蓋移動應用、開發工具、移動遊戲及引擎、智慧硬體、物聯網等方方面面。如果您想投稿、參與內容翻譯工作,或尋求近匠報道,請傳送郵件至tangxy#csdn.net(請把#改成@)。 

Android平臺的誕生為手機智慧化的普及立下汗馬功勞,但其最大的缺點也越來越凸顯,那就是碎片化嚴重:裝置繁多、品牌眾多、版本各異,晶片、攝像頭、解析度不統一等等,這些都逐漸成為Android系統發展的障礙,碎片化嚴重不僅造成Android系統混亂,也導致Android應用隱形開發成本的增多。本文中詳細介紹了Android琳琅滿目的適配問題。

一、個性化十足的Launcher

快捷方式雖然看起來只是一個很小的功能點,但是它涉及到的機型適配問題很多。

快捷方式建立程式碼:

  1. ntent addShortCut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");  
  2. addShortCut.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);  
  3. // 不允許重複建立
  4. addShortCut.putExtra("duplicate"false);  
  5. addShortCut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);  
  6. addShortCut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);  
  7. sendBroadcast(addShortCut);  

1. 無法建立快捷方式

越來越多的手機廠商取消了快捷方式的概念,導致我們無法通過程式碼建立一個自己真實需要的快捷方式,資料顯示,這樣的手機約佔13%。

 

2. 重複建立快捷方式

通常情況下,我們是不希望自己的快捷方式被重複建立。使用addShortCut.putExtra("duplicate", false);方法就能達到目的, 但是市面上至少有8%的手機,即使設定了duplicate為false,還是可以重複建立快捷方式。

代表手機品牌為:華為、中興、HTC。

Android Launcher原始碼:

 

2.1 重複建立快捷方式的解決方案V1.X

我們最早使用的解決快捷方式重複建立的方法是:在建立快捷方式前先執行刪除操作。這種方式其實很聰明,因為即使是在快捷方式不存在的情況下執行刪除操作也不會有任何異常。這樣看來問題解決得太輕鬆了,但是遺憾的是刪除快捷方式同樣存在適配問題,資料顯示大約21%的手機無法正常刪除快捷方式。

另外一種方法是:自行儲存快捷方式的建立記錄,通過一個欄位來記錄快捷方式是否已經建立過了,以此來決定是否建立新的快捷方式。這種做法也是因為出現快捷方式無法刪除情況後對解決方案進行了一個小的升級,雖然可以解決問題,但是如果程式被清除了資料,那麼一切都亂了,還是無法徹底的規避重複的問題。

2.2 重複建立快捷方式的解決方案V2.X

遇到難解的問題還是看看原始碼吧,Android的Launcher原始碼在建立快捷方式的時候不僅會判斷duplicate的值,還會在資料庫中查詢一下將要被建立的快捷方式是否已經存在,我們也照做就OK了。

 

此外,我們也注意到,查詢資料庫的時候訪問地址URI是一個很重要的因素,問題是資料庫的URI比較多,Android標準的URI就有3個:

  • 2.2版本以前的URI是:content://com.android.launcher.settings/favorites?notify=true
  • 2.2~4.3版本的URI是:content://com.android.launcher2.settings/favorites?notify=true
  • 4.4版本以上的目前都是:content://com.android.launcher3.settings/favorites?notify=true

不僅僅Android自己的Launcher資料庫地址眾多,廠商自己定義的地址就更加豐富多彩,如OPPO R827T的訪問URI為:content://com.oppo.launcher.settings /favorites?notify=true;HTC Z715e的訪問地址為: content://com.htc.launcher.settings/favorites?notify=true。事實上 遠遠不止這些,還有不計其數的第三方Launcher應用,很多開發者也會修改資料庫訪問地址,目前僅我們掌握的不同訪問地址就有多達40種左右。

  • 通過許可權查詢URI:

通過資料庫的讀寫許可權來查詢對應的URI相信大家也不陌生,感覺上像是找到了終極的解決方案,且看下去...

 

  1. 問題一:如果使用完整的許可權進行查詢--許可權眾多,我們目前掌握的超過50種。
  2. 問題二:如果使用不完整的許可權進行查詢(READ_SETTINGS)對應關係複雜,大約有 32% 的手機會對應兩個以上的URI。

例如:

GT-I8262D:

authority:com.sec.android.app.launcher.settings ReadPermission:com.android.launcher.permission.READ_SETTINGS 
authority:com.sec.android.app.launcher.settings.id ReadPermission:com.android.launcher.permission.READ_SETTINGS

Lenovo A278t:

authority:com.aspire.mm.Settings ReadPermission:com.aspire.mm.permission.READ_SETTINGS 
authority:com.huaqin.launcherEx.settings ReadPermission:com.huaqin.launcherEx.permission.READ_SETTINGS 
authority:com.huaqin.thememgr.Settings ReadPermission:com.huaqin.thememgr.permission.READ_SETTINGS

二、多姿多彩的Camera

1. Intent呼叫手機內相機程式

 

如果我們設定了照片的儲存路徑,那麼很可能會遇到一下三種問題:

  • 問題一:onActivityResult方法中的data返回為空(資料表明,93%的機型的data將會是Null,所以如果我們指定了路徑,就不要使用data來獲取照片,起碼在使用前要做空判斷)。
  • 問題二:照片無法儲存。

如果自定義儲存路徑是/mnt/sdcard/lowry/,而手機SD卡下在拍照前沒有名為lowry的資料夾,那麼部分手機拍照後圖片不會儲存,導致我們無法獲得照片,大多數手機的相機遇到資料夾不存在的情況都會自己創建出不存在的資料夾,而個別手機卻不會建立,其代表機型為:三星I8258、華為H30-T00、紅米等。

解決的方法就是在指定儲存路徑前先判斷路徑中的資料夾是否都存在,不存在先建立再呼叫相機。

  • 問題三:照片可以儲存,但是名字不對。

file:///mnt/sdcard/123 1.jpg,由於URI的fromFile方法會將路徑中的空格用“%20”取代。

其實對於大多數的手機這都不算事,手機在解析儲存路徑的時候都會將“%20”替換為空格,這樣實際上最終的照片名字還是我們當初指定的名字:123 1.jpg,遺憾的是個別手機(如酷派7260)系統自帶的相機沒有將“%20”讀成空格,拍照後的照片的名字是123%201.jpg,我們用路徑“file:///mnt/sdcard/123 1.jpg”能找到照片才怪!

 

 

 

總結:

(1)使用onActivityResult中的intent(data)前要做空判斷。 
(2)指定拍照路徑時,先檢查路徑中的資料夾是否都存在,不存在時先建立資料夾再呼叫   相機拍照。 
(3)指定拍照儲存路徑時,照片的命名中不要包含空格等特殊符號。

2. 通過Camera的open方法呼叫手機攝像頭

2.1 連續自動對焦crash

原因:第一次對焦未結束,應用層又發起的第二次對焦,引起對焦失敗。

 

解決方案一:傳入AutoFocusCallback;

 

解決方案二:延時操作;

解決方案三:異常捕獲。

2.2 攝像頭個數判斷錯誤

現象:當我們使用Camera.getNumberOfCameras()方法檢測攝像頭數量時返回的結果不準確,如果我們嘗試開啟一個不存在的攝像頭肯定會丟擲異常,這也提醒我們在開啟Camera攝像頭時需要加異常保護。

代表機型:聯想278T、酷派8022

 

2.3 閃光燈的判斷

我們常用的判斷手機是否有閃光燈的方法應該有以下兩種:

判斷是否支援閃光燈方法一:使用getSupportedFlashModes方法;

 

判斷是否支援閃光燈方法二:通過PackageManager判斷。

 

方法一有3.7%的機器結果錯誤,無法準確地判斷出手機是否有閃光燈,主要的品牌包含:酷派、天語、聯想、三星等。方法二有9.7%的機器結果錯誤,主要品牌包含:VIVO、金立、酷派、天語、朵唯、三星等。

我們建議在判斷手機是否有閃光燈的時候將這兩種方法聯合使用,出現錯誤的概率將大大降低。

2.4 常亮狀態與其他狀態間的切換

前提條件是我們設定閃光燈為常亮(Parameters.FLASH_MODE_TORCH),並且閃光燈成功常亮。此時我們在設定閃光燈模式為Parameters.FLASH_MODE_AUTO後閃光燈依然常亮,這樣的機型約佔熱門機型的12%。遇到這種情況我們需要先設定閃光燈模式為Parameters.FLASH_MODE_OFF關閉閃光燈後再設定其他模式。

2.5 釋放Camera後閃光燈依舊閃亮

既然開了,我們就要負責關,說實話,以前這個問題根本不在我的考慮範內,因為我們在使用Camera的時候都會在Activity被銷燬或者暫停時釋放Camera。這個時候無論閃光燈是什麼狀態,都會隨著Camera的釋放而關閉。直到我遇見了OPPO R815T,我的世界觀發生了變化,這貨如果設定了閃光燈常亮,即使釋放了Camera閃光燈依舊穩穩地亮著。

而且由於Camera被釋放掉了,你再也沒辦法關閉閃光燈了,關閉App、解除安裝App,你還是扣電池關機吧.....所以,如果你的程式中有設定閃光燈為常亮狀態的操作,建議在釋放Camera前先將閃光燈設定為關閉(Parameters.FLASH_MODE_OFF)狀態。

2.6 CameraInfo的另類情況

官方文件中有關於調整相機預覽角度的例子:

 

在這個例子中CameraInfo非常重要,最終的角度計算就是根據CameraInfo中orientation值得到的,所以如果這個值不準確的話,那麼我們的角度就有可能出現錯誤。

VIVO V1手機第一次獲取CameraInfo的orientation值是90,而當執行了mCamera = Camera.open();之後再獲取CameraInfo的orientation值就是0,而且以後獲取的都是 0 ,除非重啟手機。

無論是這款手機上的哪個應用,只要執行了一次Camera.open()之後,其他所有程式中獲取CameraInfo的orientation都是是0。

手機自帶的相機卻能很好的使用反編譯系統相機後果然發現系統相機並沒有像官方給出的例子來進行角度的矯正。

 

 

 

 

解決方案:

  1. 按照此手機系統相機的做;
  2. 對該手機CameraInfo的orientation值寫死為90。

三、不止是2的雙卡

雙卡的問題解決的基本思路:

  1. 推斷:手機內建的系統APP都可以正常使用這些功能,因此肯定存在廠商自定義API來實現這些功能;
  2. 反編譯:Framework、系統App、系統資料庫;
  3. 定位:TelephoneManager擴充套件、SMSManager擴充套件、電話服務擴充套件、簡訊服務擴充套件、資料庫欄位擴充套件。

四、UI適配

說到UI適配其實很是讓人頭疼,下面的圖片是某個產品為了進行UI適配所做的工作,可以看出相當繁瑣。

 

除了解析度的適配,有時候佈局檔案中的某個標籤還會引起一些問題,我們先看下面一段佈局程式碼:

 

正確結果:

 

錯誤結果:

 

這就是因為Android 3.0以下版本在FrameLayout中使用layout_marginTo標籤,必須要設定gravity才能生效。

那麼如何解決這個問題呢?在設定android:layout_marginTop的元件中再設定一下 android:layout_gravity="top"即可。

五、還有更奇葩的

1. 廠商的抽象方法

如果你需要實現InputConnection介面,那麼你一定要注意下面這個很奇葩的異常:

 

 

反編譯了下此款手機的Framework,發現廠商在InputConnection介面中增加了一個抽象方法performYLPrivateCommand。


2. 距離感測器


2.1 不同手機event.values[0]值簡直是千變萬化

簡單說幾個有代表性的:

  1. 一部分手機比較正常,靠近時為0遠離時為1(0,1);
  2. 有點小個性的手機數值將變大,比如(0,100),(3,5),(3,100)等等;
  3. 213手機的數值就比較莫名其妙,(1.001,5.003),你是表明精確度高?

2.2 數值與遠近關係不統一

既然我們是通過數值來判斷當前是否出於近耳狀態,那麼是不是應該這個數值的大小是有說道的?靠近時的數值小一點,遠離時的數值大一些,起碼我見過的99%的手機是這樣子的。但是就有幾款神經病手機(100W)偏偏是靠近時的數值比遠離時的數值大,這是個坑,開發者要注意~~!!

2.3 getMaximumRange方法返回值不對

有一句API:SensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY).getMaximumRange(), 文件解釋這個應該獲取的是感測器數值變化的最大範圍,比如如果靠近時的值是0,遠離時的值是1。那麼getMaximumRange()的值應該是1才不會影響我們的判斷,我這裡僅僅是從API角度和我們日常的使用習慣來說的,如果不是這樣的規律,就會對我們的程式設計造成麻煩。