前言
在完美完成过年增肥任务之后,新的一年又得投入到工作当中了,今天是新年的第一篇博客,我们来讨论一下Android开发经久不变的兼容性问题。
国内有很多的厂商都定制了自己的安卓系统,谁也不知道会他们私底下都做了哪些操蛋的修改,再加上安卓版本的更新周期目前还是比较稳定,几乎每一年都会发布新的版本,例如去年的8.0。目前国内的Android系统主要是5.0和6.0,少量的4.x和7.0,掰掰手指头就5、6个,所以如何做好兼容性问题,一直是开发者,也是老板最关心的问题。
今天我准备简单介绍几个比较细节的例子,大家一起学习讨论下。
正文
我把兼容性问题主要分为两大类:
1、功能兼容问题,主要是系统版本的新特性,api的变化,或者是sdk定制导致部分api的执行结果出现差异导致的,一般需要重写方法,或者判断系统版本特殊处理。
2、布局兼容问题,主要是不同ViewGroup的特性和某些属性不能同时使用的问题,也可能是由不同Android系统版本自带的Theme或者其他特性导致的。
功能兼容问题
获得从相册选择的图片的路径
从相册选择图片,系统会返回给我们Uri,但是个别情况我们也需要得到路径,其实这就有一个兼容性问题,在不同系统版本的手机你会发现有的能得到路径,有的不能。
这是因为不同手机可能返回的Uri的Shceme,导致出现这个问题,我遇到这个问题是很久之前了,当时没有自己写总结,这里分享一个博客链接:,供大家参考学习。
照相机拍照以及调用系统安装
从7.0以后,为了提高app安全,Android不允许app与其他app通信时,传递明文的文件路径,比较典型的例子就是app下载更新,安装的时候需要通过自己的ContentProvider对apk的路径进行加密,然后调用系统安装。
有的时候需要调用系统相机拍照,并且指定照片的保存路径。这个路径我们也必须加密,否则会直接崩溃,具体的解决办法我之前有写过:适配android7.0:获取文件的Uri。
权限
在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,你也可以按照你的需要修改其他的属性。
总结
上面的几点是我临时整理的,还有很多的内容都没有写出来,以后会慢慢补充,最后祝大家在新的一年里技术薪资双提升!!!