Android机型适配

2017/9/3 posted in  Android

多机型 UI 适配

在 Android 开发的工作日常中,UI 适配是一件比较重要的事情。一般开发新的功能,有新的页面,最终开发完成发布之前,都需要让设计师来审 UI 效果。

而有时候,明明我们已经在一款标准的 3 倍手机上,做到了让设计师小姐姐满意的效果,但是当换了另外一台手机的时候,效果又不那么如意。

传统的做法是,拿到效果不对的手机,单独适配看看是不是哪里布局写的不对导致效果不正确,然后改正它。但是总能碰到我们拿不到真实设备的情况。

本文就介绍一个命令,可以修改你当前设备的参数,来模拟出目标设备的显示效果,让你独立就把多机型 UI 效果确认的事情给做了。

WM 命令

什么是 WM 命令

说的这么厉害,实际上需要使用的就是一行 wm 命令,可以通过 adb shell 来操作它。

wm 命令主要用来帮助我们修改一些屏幕的参数,先来看看它的文档。使用 adb shell wm 命令,可以方便的查看 wm 的文档描述

20171016150816609742581.jpg
20171016150816609742581.jpg

可以看到,wm 命令可以帮助我们修改当前设备的 size 、 density 等参数,而这两个参数就是决定了当前设备的显示效果。

WM 的命令

从上面的文档可以看出,它其实支持的命令还是挺多的,但是有一些根本用不上,是需要系统权限的,所以正常来说我们是无法使用的。那么,还是介绍几个比较实用的命令。

以下例子都是在一款标准 density 为 3 的设备上运行的,屏幕像素为 1080px * 1920px。

wm size

wm size 命令,可以用来查询和修改当前设备的尺寸,如果想要修改尺寸,可以使用 px 或者 dp 为单位(默认为 px),使用 x 进行连接,这个其实看看 wm size 的输出,你就清楚了。

20171016150816615239717.jpg
20171016150816615239717.jpg

在这里的例子中,做了几件事情:

  • 使用 wm size 命令查看当前设备的尺寸。
  • 使用 wm size 命令将当前尺寸修改成了 1000x2000。
  • 再次使用 wm size 命令查看当前设备的尺寸。
  • 最后使用 wm size reset 命令,将屏幕尺寸还原。
  • 再用 wm size 命令,查看还原后的尺寸。

这几个步骤就已经涵盖了 wm size 命令的所有使用步骤,可以使用它查看当前屏幕尺寸,并且修改屏幕尺寸和还原它。

wm density

wm density 命令,可以用来查看和修改当前设备的 density 参数。

使用起来很简单,直接上命令了。

20171016150816618347092.jpg
20171016150816618347092.jpg

在这个例子中,我做了几件事情:

  • 使用 wm density 查看当前设备的 density。
  • 在使用 wm density 命令,修改当前设备的 density 为 420。
  • 然后再使用 wm density 查看修改后的参数。
  • 最后使用 wm density reset 将设备的 density 还原。
  • 最后再查看一下还原后的值。

wm overscan

wm overscan 命令,可以修改当前设备的屏幕内边距。其实我觉得这个命令没什么用处,但是还是演示一下如何使用。

adb shell wm overscan 20,20,20,20
adb shell wm overscan reset

在这个例子中,做了几件事情:

  • 使用 wm overscan 修改边距为 20 。
  • 然后使用 wm overscan reset 将其还原。

WM 命令的原理

wm 命令的逻辑非常的简单,它实际上是和 WindowManagerService 进行交互,通过 WMS 来修改屏幕相关的一些参数。

这里就不介绍完整的 wm 源码了,就以一个方法举例来看看 wm 的逻辑。这里就以 wm size命令举例好了。

首先,看看 wm 命令的入口。

@Override
public void onRun() throws Exception {
    mWm = IWindowManager.Stub.asInterface(ServiceManager.checkService(Context.WINDOW_SERVICE));
    if (mWm == null){
        System.err.println(NO_SYSTEM_ERROR_CODE);
        throw new AndroidException("Can`t connect to window manager;is the system running?");
    }
    
    String op = nextArgRequired();
    
    if (op.equals("size")) {
        runDisplaySize();
    } else if (op.equals("overscan")){
        runDisplayOverscan();
    } else if (op.equals("scaling")){
        runDisplayScaling();
    } else if (op.equals("screen-capture")){
        runSetScreenCapture();
    } else if (op.equals("dismiss-keyguard")){
        runDismissKeyguard();
    } else {
        showError("Error: unknown command ` " + op + "`");
        return;
    }
}

这里会初始化 mWm ,并且根据输入的参数,调用不同的方法,如果输入的是 size ,则会调用 runDisplaySize() 方法。

private void runDisplaySize() throws Exception {
    String size = nextArg();
    int w,h;
    if (size == null){
        Point initialSize = new Point();
        Point baseSize = new Point();
        try {
            mWm.getInitialDisplaySize(Display.DEFAULT_DISPLAY, initialSize);
            mWm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, baseSize);
            System.out.println("Physical size:" + initialSize.x + "x" + initialSize.y);
            if (!initialSize.equals(baseSize)){
                System.out.println("Override size:" + baseSize.x + "x" + baseSize.y);
            }
        } catch (RemoteException e){
        }
        return;
    } else if ("reset".equals(size)){
        w = h = -1;
    } else {
        int div = size.indexOf('x');
        if (div <= 0 || div >= (size.length() - 1)){
            System.err.println("Error: bad size" + size);
            return;
        }
        String wstr = size.substring(0,div);
        String hstr = size.substring(div + 1);
        try {
            w = parseDimension(wstr);
            h = parseDimension(hstr);
        } catch (NumberFormatException e){
            System.err.println("Error: bad number" + e);
            return;
        }
    }
    
    try {
        if (w >= 0 && h >= 0){
            mWm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, w, h);
        } else {
            mWm.clearForcedDisplaySize(Display.DEFAULT_DISPLAY);
        }
    } catch (RemoteException e) {
    }
}

在 runDisplaySize() 方法中,会先判断 size 命令后面有没有跟其它参数,如果没有,则认为是一次查询操作,会将查询到的结果输出。

而如果是 reset 方法的话,就将 w 、h 这两个代表宽高的值,设置为 -1,最终会通过 mWm.clearForcedDisplaySize() 方法,将屏幕的 size 还原。

否者就从输入的参数中,通过 x 进行分割,拿到我们输入的宽高。最终通过 mWm.setForcedDisplaySize() 方法,将当前屏幕的 size 修改成我们输入的结果。

在设置size 的时候,还会通过 parseDimens() 方法,重新计算一下输入的尺寸。

private int parseDimension(String s) throws NumberFormatException {
if (s.endsWith("px")) {
return Integer.parseInt(s.substring(0, s.length() - 2));
}
if (s.endsWith("dp")){
int density;
try {
density = mWm.getBaseDisplayDensity(Display.DEFAULT_DISPLAY);
} catch (RemoteException e) {
density = DisplayMetrics.DENSITY_DEFAULT;
}
return Integer.parseInt(s.substring(0,s.length() - 2)) * density / DisplayMetrics.DENSITY_DEFAULT;
}
return Integer.parseInt(s);
}

从这里可以看出,它支持两个尺寸,px 和 dp ,如果如果不指定尺寸,则默认认为是 px 。

到这里就完成了一次通过 wm size 命令,修改设备屏幕参数的全过程,跟多细节可以自行查阅源码。

源码的在线地址:

androidxref.com/7.1.1_r6/xr…

实施修改设备参数

既然已经讲清楚 wm 命令的所有细节了,那么我们只需要拿到我们需要适配的设备的 size 和 density。就可以在我们自己的设备上模拟出对应的效果。

我这里收集了一些比较常用的设备参数,可以供大家参考一下。

  • 魅族MX3:440,1080x1800
  • 一加5:420,1080x1920
  • 红米NOTE,320,720x1280
  • 华为MATE7,480,1080x1812
  • MOTO-G5,480,1080x1920
  • Moto-tx1095,432,1080x1790
  • Nexus5x,420,1080x1794
  • Nexus6p,560,1440x2392
  • 奇酷360,460,1080x1920
  • VivoX5,320,720x1280

当然,最重要的是拿到公司各种 CEO、CTO、CxO 正在使用的手机参数,进行一波适配,哈哈。

下面,我们随便挑两个设备的参数进行修改,然后看看运行后的效果。

命令如下:

// 修改成 一加5

adb shell wm density 420

adb shell wm size 1080x1920

// 修改成 Moto-tx1095

adb shell wm density 432

adb shell wm size 1080x1790

下面,从做到右,分别是原本的尺寸、一加5、Moto-tx1095。

20171016150816770425798.jpg
20171016150816770425798.jpg

其实看着差异不大,只能说桌面布局写的挺好的,所以说,如果布局写的好的话,是可以避免在不同设备上看着很明显的不同的问题 ,就像现在这样。

题外话

实际上 wm 命令,也是有一些其它问题的,例如有时候通过 wm 命令修改 UI 的的时候,刷新会不及时,会卡顿,所以选择一个性能比较好的手机是一个缓解的办法。或者修改参数之后,退出 App 重新进也是一个不错的办法。

如果调整到一个太特殊的参数,例如 200x1000 这种,可能渲染起来会很奇怪。但是如果只是做一些常规设备参数的修改,只要修改值在合理的范围内,一般都是可以的。