Android相容性小總結(一)
前言
在完美完成過年增肥任務之後,新的一年又得投入到工作當中了,今天是新年的第一篇部落格,我們來討論一下Android開發經久不變的相容性問題。
國內有很多的廠商都定製了自己的安卓系統,誰也不知道會他們私底下都做了哪些操蛋的修改,再加上安卓版本的更新週期目前還是比較穩定,幾乎每一年都會發布新的版本,例如去年的8.0。目前國內的Android系統主要是5.0和6.0,少量的4.x和7.0,掰掰手指頭就5、6個,所以如何做好相容性問題,一直是開發者,也是老闆最關心的問題。
今天我準備簡單介紹幾個比較細節的例子,大家一起學習討論下。
正文
我把相容性問題主要分為兩大類:
1、功能相容問題,主要是系統版本的新特性,api的變化,或者是sdk定製導致部分api的執行結果出現差異導致的,一般需要重寫方法,或者判斷系統版本特殊處理。
2、佈局相容問題,主要是不同ViewGroup的特性和某些屬性不能同時使用的問題,也可能是由不同Android系統版本自帶的Theme或者其他特性導致的。
功能相容問題
獲得從相簿選擇的圖片的路徑
從相簿選擇圖片,系統會返回給我們Uri,但是個別情況我們也需要得到路徑,其實這就有一個相容性問題,在不同系統版本的手機你會發現有的能得到路徑,有的不能。
照相機拍照以及呼叫系統安裝
從7.0以後,為了提高app安全,Android不允許app與其他app通訊時,傳遞明文的檔案路徑,比較典型的例子就是app下載更新,安裝的時候需要通過自己的ContentProvider對apk的路徑進行加密,然後呼叫系統安裝。
有的時候需要呼叫系統相機拍照,並且指定照片的儲存路徑。這個路徑我們也必須加密,否則會直接崩潰,具體的解決辦法我之前有寫過:
許可權
在Android 6.0以及國內部分手機在Android 5.0開啟了許可權申請,部分敏感許可權需要手動申請,所以如果你需要使用某些許可權,一定要記得申請,網上這部分資料非常多,這裡就不多說了。
ScrollView滑動到底部的問題
這個問題是最近才發現的,看來測試機多了還是有好處的,一般我們監聽到ScrollView是否滑動到底部通過重寫
onOverScrolled方法,判斷clampedY是否是true,如果是true,表示滑動到了底部。
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)
但是在部分手機上clampedY始終返回false,例如我發現的錘子手機。
解決辦法:不僅要判斷clampedY是否等於true,還要判斷ScrollView的scrollY + getHeight + paddingTop + paddingBottom是否等於第一個子View的高度,下面貼出程式碼:
@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
if (scrollY != 0 && null != onScrollToBottom) {
// 如果是false,需要額外判斷,解決部分手機的相容問題,例如錘子
if (!clampedY) {
// 解決在錘子
if (getScrollY() + getHeight() - getPaddingTop() - getPaddingBottom() == getChildAt(0).getHeight()) {
onScrollToBottom.onScrollBottomListener(true);
return;
}
}
onScrollToBottom.onScrollBottomListener(clampedY);
}
}
Theme相容問題
使用Theme,推薦使用Theme.AppCompat下的主題,這樣避免相容性問題,如果你看到了你的錯誤提示:
You need to use a Theme.AppCompat theme (or descendant) with this activity
說明你也是一個有故事的人了,我遇到多發生在Android 8.0 和 華為手機。
佈局相容問題
RelativeLayout子View的padding、margin和center屬性的衝突
首先我們看一段程式碼:<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#000000"
android:paddingRight="30dp"
android:paddingBottom="10dp">
<TextView
android:layout_marginTop="50dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/app_name"
android:textColor="#ffffff" />
</RelativeLayout>
在RelativeLayout設定paddingBottom=20dp,但是TextView設定了centerInParent並且設定了marginTop=50dp,會顯示出什麼效果呢?很明顯paddingBottom和marginTop都沒有生效,同樣的道理,子View的centerInHorizontal,centerInVertical屬性也是和父View的padding和自身的margin是不能同時生效的。Button的預設大小問題<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:orientation="vertical" android:gravity="center" android:layout_height="match_parent"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#00ff00" android:drawablePadding="5dp" android:text="hello button" /> <TextView android:layout_marginTop="20dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#00ff00" android:textSize="14sp" android:textColor="#000000" android:drawablePadding="5dp" android:text="hello button" /> </LinearLayout>
這裡我特意設定了text="hello button", 因為英文會讓問題更明顯,Button和TextView都是wrap_content,理論上應該是一樣的效果,看一下展示圖:
咦?很明顯結果並不和我們預料的一樣,強調一下我使用的sdk版本是26,如果你的展示效果和我的不一樣,那就說明了一個問題:Button在不同的sdk中有不同的預設樣式。
接下來我們看一看預設樣式是什麼:
// 首先我們檢視Button的構造方法,看到了Button使用了buttonStyle樣式
// 用過style切換主題的朋友對這種用法一定很熟悉,接下來我們要看看style檔案裡怎麼定義的buttonStyle
public Button(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.buttonStyle);
}
<!-- Button styles -->
// 順著主題的繼承關係,我們跟蹤到了Base.Widget.AppCompat.Button主題
<item name="buttonStyle">@style/Widget.AppCompat.Button</item>
<style name="Widget.AppCompat.Button" parent="Base.Widget.AppCompat.Button"/>
當我們繼續跟蹤程式碼的時候出現了一個選擇框:
這個主題在sdk 21以下 和sdk以上分別使用了兩種主題,我們先看看sdk 21以下的style:
<style name="Base.Widget.AppCompat.Button" parent="android:Widget">
<item name="android:background">@drawable/abc_btn_default_mtrl_shape</item>
<item name="android:textAppearance">?android:attr/textAppearanceButton</item>
<item name="android:minHeight">48dip</item>
<item name="android:minWidth">88dip</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:gravity">center_vertical|center_horizontal</item>
</style>
再看看sdk 21以上的style:
<style name="Base.Widget.AppCompat.Button" parent="android:Widget.Material.Button"/>
<!-- Bordered ink button -->
<style name="Widget.Material.Button">
<item name="background">@drawable/btn_default_material</item>
<item name="textAppearance">?attr/textAppearanceButton</item>
<item name="minHeight">48dip</item>
<item name="minWidth">88dip</item>
<item name="stateListAnimator">@anim/button_state_list_anim_material</item>
<item name="focusable">true</item>
<item name="clickable">true</item>
<item name="gravity">center_vertical|center_horizontal</item>
</style>
通過對比,我們發現兩種主題的背景,字型樣式是不同的,同時發現了有minWidth和minHeight,所以Button會有預設的大小,字母會全大寫是textAppearance中定義的,感興趣的朋友可以自己去看看裡面還定義了哪些樣式。
解決辦法:設定Button的minWidth和minHeight等於0,你也可以按照你的需要修改其他的屬性。
總結
上面的幾點是我臨時整理的,還有很多的內容都沒有寫出來,以後會慢慢補充,最後祝大家在新的一年裡技術薪資雙提升!!!