前言

在完美完成过年增肥任务之后,新的一年又得投入到工作当中了,今天是新年的第一篇博客,我们来讨论一下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,会显示出什么效果呢?

Android兼容性 安卓app兼容性_Android兼容性

很明显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,理论上应该是一样的效果,看一下展示图:

Android兼容性 安卓app兼容性_Android适配_02

咦?很明显结果并不和我们预料的一样,强调一下我使用的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"/>

当我们继续跟踪代码的时候出现了一个选择框:

Android兼容性 安卓app兼容性_android_03

这个主题在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,你也可以按照你的需要修改其他的属性。



总结

上面的几点是我临时整理的,还有很多的内容都没有写出来,以后会慢慢补充,最后祝大家在新的一年里技术薪资双提升!!!