天天看點

Android機型适配之痛

原文位址:http://www.csdn.net/article/2015-09-08/2825645/1

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

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

一、個性化十足的Launcher

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

快捷方式建立代碼:

[java]  view plain copy

  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%。

Android機型适配之痛

2. 重複建立快捷方式

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

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

Android Launcher源碼:

Android機型适配之痛

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

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

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

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

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

Android機型适配之痛

此外,我們也注意到,查詢資料庫的時候通路位址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相信大家也不陌生,感覺上像是找到了終極的解決方案,且看下去...

Android機型适配之痛
  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調用手機内相機程式

Android機型适配之痛

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

  • 問題一: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”能找到照片才怪!

Android機型适配之痛
Android機型适配之痛
Android機型适配之痛

總結:

(1)使用onActivityResult中的intent(data)前要做空判斷。 

(2)指定拍照路徑時,先檢查路徑中的檔案夾是否都存在,不存在時先建立檔案夾再調用   相機拍照。 

(3)指定拍照存儲路徑時,照片的命名中不要包含空格等特殊符号。

2. 通過Camera的open方法調用手機攝像頭

2.1 連續自動對焦crash

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

Android機型适配之痛

解決方案一:傳入AutoFocusCallback;

Android機型适配之痛

解決方案二:延時操作;

解決方案三:異常捕獲。

2.2 攝像頭個數判斷錯誤

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

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

Android機型适配之痛

2.3 閃光燈的判斷

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

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

Android機型适配之痛

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

Android機型适配之痛

方法一有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的另類情況

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

Android機型适配之痛

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

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

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

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

Android機型适配之痛
Android機型适配之痛
Android機型适配之痛
Android機型适配之痛

解決方案:

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

三、不止是2的雙卡

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

  1. 推斷:手機内置的系統APP都可以正常使用這些功能,是以肯定存在廠商自定義API來實作這些功能;
  2. 反編譯:Framework、系統App、系統資料庫;
  3. 定位:TelephoneManager擴充、SMSManager擴充、電話服務擴充、短信服務擴充、資料庫字段擴充。

四、UI适配

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

Android機型适配之痛

除了分辨率的适配,有時候布局檔案中的某個标簽還會引起一些問題,我們先看下面一段布局代碼:

Android機型适配之痛

正确結果:

Android機型适配之痛

錯誤結果:

Android機型适配之痛

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

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

五、還有更奇葩的

1. 廠商的抽象方法

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

Android機型适配之痛
Android機型适配之痛

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

Android機型适配之痛

2. 距離傳感器

Android機型适配之痛

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角度和我們日常的使用習慣來說的,如果不是這樣的規律,就會對我們的程式設計造成麻煩。