CrashDigger1.2介绍

CrashDigger1.2介绍


author huizhong; QQ28073223
  • 修订记录:
  • 1.0:增加组件安全隐患测试内容
  • 1.1:新增基础性能测试,包含流量、cpu、内存、电量信息;新增流畅性性能测试内容,支持测试页面帧率监控

组件crash隐患

android应用内部的Activity、Service、Broadcast Receiver等,他们通过Intent通信,组件间需要通信就需要在Androidmanifest.xml文件中暴露组件,前面提到的风险就有可能是不恰当的组件暴露引起的。每个组件都可在 AndroidManifest.xml 里通过属性 exported 被设置为私有或公有。私有或公有的默认设置取决于此组件是否被外部使用;例如某组件设置了 intent-filter 意味着该组件可以接收 intent,可以被其他应用访问.

Intent的两种基本用法:一种是显式的Intent,即在构造Intent对象时就指定接收者;另一种是隐式的Intent,即Intent的发送者在构造Intent对象时,并不知道也不关心接收者是谁,有利于降低发送者和接收者之间的耦合。显示调用和隐式调用都能过在不同应用间传递数据。

可能产生的风险:

1、恶意调用
2、恶意接受数据
3、仿冒应用,例如(恶意钓鱼,启动登录界面)
4、恶意发送广播、启动应用服务。
5、调用组件,接受组件返回的数据
6、拦截有序广播

【组件crash隐患使用方法】

  1. 安装被测试app以及crashDigger,启动后切换到「组件安全隐患测试」tab。点击开始测试
此处输入图片的描述
此处输入图片的描述
  1. 安装待测试平安系的app显示如下图
此处输入图片的描述
此处输入图片的描述
  1. 点击你要测试的app,如人寿进入。选择需要测试的组件以及实例类型,分别测试。
此处输入图片的描述
此处输入图片的描述
  1. 观察响应结果以及日志可能出现的问题有,crash,Anr, 假死(定屏,点击可退出),白屏等
此处输入图片的描述
此处输入图片的描述
此处输入图片的描述
此处输入图片的描述

出现白屏事例如下:

此处输入图片的描述
此处输入图片的描述

ANR 事例如下

05-13 13:37:09.981: E/ActivityManager(1194): ANR in com.xfdream.pinganyh (com.xfdream.pinganyh/.yxapi.YXEntryActivity)
05-13 13:37:09.981: E/ActivityManager(1194): Reason: keyDispatchingTimedOut
05-13 13:37:09.981: E/ActivityManager(1194): Load: 2.18 / 1.98 / 1.93
05-13 13:37:09.981: E/ActivityManager(1194): CPU usage from 18490ms to 2205ms ago:

【crashDigger组件安全扫描原理】

crashDigger会扫描全部app外露接口,同时构造异常intent,统一播报给目标app,播报完成后即收集异常数据。对手机内已有的app产品全部暴露接口进行intent调起的遍历测试,测试对象包括外露的activity、broadcast、service、provider组件

【实现方法】

http://developer.android.com/reference/android/content/pm/PackageManager.html

  • PackageManger 类

Android提供了一个PackageManger管理类,它的主要职责是管理应用程序包。 通过它,我们就可以获取应用程序信息.


此处输入图片的描述
此处输入图片的描述

说明: 获得已安装的应用程序信息 。可以通过getPackageManager()方法获得Receiver,Provider,Activity,Services的Intent。

其他主要相关类介绍如下:

  • PackageItemInfo类

说明: AndroidManifest.xml文件中所有节点的基类,提供了这些节点的基本信息:a label、icon、meta-data。它并不直接使用,而是由子类继承然后调用相应方法。

常用字段

public int icon获得该资源图片在R文件中的值 (对应于android:icon属性)
public int labelRes 获得该label在R文件中的值(对应于android:label属性)
public String name  获得该节点的name值 (对应于android:name属性)
public String packagename  获得该应用程序的包名 (对应于android:packagename属性)

常用方法:

Drawable  loadIcon(PackageManager pm)获得当前应用程序的图像
CharSequence  loadLabel(PackageManager pm)获得当前应用程序的label
  • ActivityInfo类 继承自 PackageItemInfo

说明:
获得应用程序中<activity/>或者 <receiver />节点的信息 。我们可以通过它来获取我们设置的任何属性,包括theme 、launchMode、launchmode等

常用方法

继承自PackageItemInfo类中的loadIcon()和loadLabel() 
  • ServiceInfo 类

说明:
同ActivityInfo类似,同样继承自PackageItemInfo,只不过它表示的是<service>节点信息

  • ApplicationInfo类 继承自 PackageItemInfo

说明:
获取一个特定引用程序中<application>节点的信息。

  • ResolveInfo类

说明:
根据<intent>节点来获取其上一层目录的信息,通常是<activity>、<receiver>、<service>节点信息。

常用字段:

public  ActivityInfo  activityInfo获取ActivityInfo对象,即<activity>或<receiver >节点信息
public ServiceInfo   serviceInfo获取ServiceInfo对象,即<service>节点信息

常用方法:

Drawable loadIcon(PackageManager pm)  获得当前应用程序的图像
CharSequence loadLabel(PackageManager pm)  获得当前应用程序的label
  • PackageInfo类

说明:
手动获取AndroidManifest.xml文件的信息 。

常用字段:

public String packageName 包名
public ActivityInfo[]  activities  所有<activity>节点信息
public ApplicationInfo applicationInfo <application>节点信息,只有一个
public ActivityInfo[] receivers   所有<receiver>节点信息,多个
public ServiceInfo[] services   所有<service>节点信息 ,多个
  • PackageManger 类

说明: 获得已安装的应用程序信息。可以通过getPackageManager()方法获得。

常用方法:

public abstract PackageManager  getPackageManager()
    功能:获得一个PackageManger对象
public abstrac  tDrawable getApplicationIcon(StringpackageName)
    参数: packageName 包名
    功能:返回给定包名的图标,否则返回null
public abstract ApplicationInfogetApplicationInfo(String packageName, int flags) 
    参数:packagename 包名
    flags 该ApplicationInfo是此flags标记,通常可以直接赋予常数0即可功能:返回该ApplicationInfo对象
public abstract List<ApplicationInfo>  getInstalledApplications(int flags)
    参数:flag为一般为GET_UNINSTALLED_PACKAGES,那么此时会返回所有ApplicationInfo。我们可以对ApplicationInfo的flags过滤,得到我们需要的。
    功能:返回给定条件的所有PackageInfo
public abstract List<PackageInfo>  getInstalledPackages(int flags) 
    参数如上,
    功能:返回给定条件的所有PackageInfo
public abstractResolveInfo  resolveActivity(Intent intent, int flags)
    参数:  intent 查寻条件,Activity所配置的action和category flags: MATCH_DEFAULT_ONLY :Category必须带有CATEGORY_DEFAULT的Activity,才匹配GET_INTENT_FILTERS:匹配Intent条件即可GET_RESOLVED_FILTER :匹配Intent条件即可
    功能 :返回给定条件的ResolveInfo对象(本质上是Activity) 
public abstract  List<ResolveInfo>  queryIntentActivities(Intent intent, int flags)
    参数同上
    功能 :返回给定条件的所有ResolveInfo对象(本质上是Activity),集合对象
public abstract ResolveInfo  resolveService(Intent intent, int flags)
    参数同上
    功能 :返回给定条件的ResolveInfo对象(本质上是Service)
public abstract List<ResolveInfo> queryIntentServices(Intent intent, int flags)
    参数同上
    功能 :返回给定条件的所有ResolveInfo对象(本质上是Service),集合对象

【修改建议和方法】

  • 最小化组件暴露

不参与跨应用调用的组件添加android:exported="false"属性,这个属性说明它是私有的,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。

 - <activity  
 -           android:name=".LoginActivity"  
 -           android:label="@string/app_name"  
 -           android:screenOrientation="portrait"   
 -           android:exported="false">  

私有组件此时Activity只能被自身app启动。(同user id或者root也能启动)私有Activity不能被其他应用启动相对安全。

  • 设置组件访问权限

组件添加android:permission属性。

 - <activity android:name=".Another" android:label="@string/app_name"  
 -           android:permission="com.test.custempermission">  
</activity>  

声明< permission>属性

1.  <permission android:description="test"    
2.          android:label="test"    
3.          android:name="com.test.custempermission"    
4.          android:protectionLevel="normal">    
5.          </permission> 

****protectionLevel有四种级别normal、dangerous、signature、signatureOrSystem。signature、signatureOrSystem时,只有相同签名时才能调用。****

调用组件者声明<uses-permission>

 <uses-permission android:name="com.test.custempermission" />   
  • 暴露组件的代码检查

Android 提供各种 API 来在运行时检查、执行、授予和撤销权限。这些 API是 android.content.Context 类的一部分,这个类提供有关应用程序环境的全局信息。

 - if (context.checkCallingOrSelfPermission("com.test.custempermission")  
 -         != PackageManager.PERMISSION_GRANTED) {  
 -             // The Application requires permission to access the    
 -             // Internet");  
 - } else {  
 -     // OK to access the Internet  
 -  }  
  • Activity安全

创建 Activity时:

1、不指定 taskAffinity //task 管理 Activity。task 的名字取决于根 Activity的 affinity。默认设置中 Activity 使用包名做为 affinity。task 由 app 分配,所以一个应用的 Activity 在默认情况下属于相同 task。跨 task 启动 Activity 的 intent 有可能被其他 app 读取到。
2、不指定 lanchMode //默认 standard,建议使用默认。创建新 task 时有可能被其他应用读取 intent的内容。
3、设置 exported 属性为false
4、谨慎处理从 intent 中接收的数据,不管是否内部发送的 intent
5、敏感信息只能在应用内部操作

使用 Activity时:

6、开启Activity时不设置 FLAG_ACTIVITY_NEW_TASK 标签//FLAG_ACTIVITY_NEW_TASK 标签用于创建新 task(被启动的 Activity 并未在栈中)。
7、开启应用内部 Activity 使用显示启动的方式
8、当 putExtra() 包含敏感信息目的应是 app 内的 Activity
9、谨慎处理返回数据,即可数据来自相同应用

公开暴露的 Activity 组件,可以被任意应用启动:
创建 Activity:

1、设置 exported 属性为 true
2、谨慎处理接收的 intent
3、有返回数据时不应包含敏感信息

使用 Activity:

4、不应发送敏感信息
5、当收到返回数据时谨慎处理
  • Service 安全

通常 Service 执行的操作比较敏感,如更新数据库,提供事件通知等,因此一定要确保访问 Service 的组件有一定权限(也就是给 Service 设置权限)。

在 AndroidManifest.xml 里给 Service 设置权限(可自定义)。一般设置 exported 属性为 false(或没有 intent-filter);如果需要给别的 app 访问即此属性设置为true,最好做敏感操作的时候通过checkCallingPermission() 方法来提供权限检测。

不要轻易把 Intent 传递给公有的未知名的 Service;最好在所传递的 Intent 中提供完整类名,或者在 ServiceConnection的onServiceConnected(ComponentName, Ibinder)里检验 Service 的包名。

  • Content Provider 安全

Content Provider 为其他不同应用程序提供数据访问方式,需要更复杂的安全措施保护。读写权限分开。一旦应用程序来访,Content Provider需要对权限检测,只有拥有只读/只写权限才允许建立连接,否则抛出 SecurityException。

只读/只写一旦实施就适用于所有数据,如果播放器只要特定音乐数据,给播放器全部访问权限,岂不是权限很大,为了细分权限粒度,可以使用 Grant-uri-permission 机制来指定一个数据集。Content Provider 可通过属性 <grant-uri-permission> 为其内的 URI 设置临时访问权限。

  • Broadcast Receiver 安全

应用通常用它来监听广播消息。
广播发送方通常选择给每个发送 Broadcast Intent 授予 Android 权限;接收方不但需要符合 Intent filter 的接收条件,还要求 Broadcast Receiver 也必须具有特定权限(给发送方授予权限要一致)才能接收(双层过滤)。

基础性能测试

【基础性能测试使用方法】

  1. 启动后切换到「基础性能」tab。点击开始测试
此处输入图片的描述
此处输入图片的描述
  1. 选择需要测试的app,点击开始测试
此处输入图片的描述
此处输入图片的描述
  1. 启动后开始执行操作,wifi ON按键用于控制wifi的关闭和开启;STOP按键可以控制关闭测试;RESET用于清除当前的数据(扫描间隔一秒,点击之后一秒之后可以看到新数据);浮层可以拖拽到你想要它存在的位置。---该部分直接复用了emmage app的功能,当前可能有兼容性问题存在
此处输入图片的描述
此处输入图片的描述

流畅度测试

流畅度是衡量app页面渲染时,是否流畅的测试。图片处理器每秒刷新的帧数(FPS),可用来指示页面是否平滑的渲染。我们看到的动态画面,是一帧帧静态画面联动起来后达到的。这利用了人眼的视觉暂留。

一秒内静态画面越多,我们眼睛的感觉就越流畅。静态画面的数量,我们叫帧数。我们看到的电影是24帧到29帧,就是一秒钟24幅静态画面,因为电影的每一帧都是模糊帧,包含一定的时间信息,所以24帧我们看着就很流畅了。

【流畅度测试使用方法】

  1. 启动后切换到「流畅度」tab。点击开始测试
此处输入图片的描述
此处输入图片的描述
  1. 选择需要测试的app,点击开始测试。
此处输入图片的描述
此处输入图片的描述
  1. 启动后开始执行操作,可以点击按键选择开始和停止测试。浮层可以拖拽到你想要它存在的位置,页面上显示当前帧率,平均帧率,最大帧率以及过去一段时间的最低帧率。最低帧率突然下降到30以下时。疑似出现页面卡顿。
此处输入图片的描述
此处输入图片的描述

分辨率兼容性测试

兼容性么,总的来说有这么几项。网络兼容性,厂商兼容性(包含cpu类型),平台兼容性。

兼容性测试就是检查软件在一个特定的硬件、软件、操作系统、网络等环境下是否能够正常地运行,检查软件之间是否能够正确地交互和共享信息。

硬件兼容性指标

  • 分辨率兼容性

智能手机分辨率的兼容性测试主要关注的是程序不同页面,弹窗,提示等在不同分辨率下显示的是否正常(包括横竖屏的切换)。
主要考虑因素如下:

a. 分辨率市场占有率topN,参考和产品线PM沟通的结果。

b. 产品用户所用分辨率topN

  • 机型兼容性

需要保证市场占用率较大的厂商机器可以正常执行产品功能,比如三星、htc,同时结合市场占有率和产品线的数据统计确定测试机型。
主要考虑因素如下:

a. 主流机型的市场占有率的top3

b. 产品线用户机型的top3

c. 特殊厂商,特殊机型。

  • 操作系统兼容性
    不同的操作系统及同一操作系统不同版本对程序处理方式可能会不同,所以要针对产品对操作系统做兼容性测试。
    主要考虑因素如下:

a. 操作系统原生版市场占有率的top3

b. 特殊厂商,自己制作了适合自己手机的rom,比如小米系统、魅族、CM等,与原生系统略有些不一样。

c. android测试,大版本覆盖到小数点后一位,如2.3.X,4.1.X,5.1.X,小版本覆盖到整数位,2.X,4.X,5.X;IOS同Android,但作为最新的系统版本必测。

软件兼容性指标

主要考察两项内容:一是软件运行需要哪些其他应用软件的支持,二是判断与其他常用软件如MS OFFICE,反病毒软件一起使用,是否造成其他软件运行错误或软件本身不能正确实现其功能。

与其他软件交互时主要有以下三个部分:

兼容类型 预期
平安APP系列:口袋银行,平安好车主 …… 相互无冲突
监控类产品:网秦、360手机卫士 相互无冲突
聊天类产品:QQ、飞信 相互无冲突
浏览器产品:UC 相互无冲突
输入法产品:QQ输入法、搜狗输入法 相互无冲突
系统工具:蓝牙助手、高级任务管理、缓存助手 相互无冲突
娱乐播放类:豆瓣电台、酷我听听、天天、优酷、QQ音乐 相互无冲突

网络兼容性指标

网络兼容性,是保证各种网络环境能够够完全覆盖,排除由于不同网络网关特殊处理导致产品功能不可用情况,同时也需要检查在各种网络下的用户体验问题。主要包括移动2g,3g,4g,联通2g,3g,4g,有鉴权的wifi和无鉴权的wifi

数据兼容性

通常指不同版本间的数据兼容性,主要检查低版本程序运行产生的数据在安装新版本后是否可用,或者正常升级的情况下,用户原始数据是否丢失。

上面废话说这么多,无非想说兼容性要测试的机器还是蛮多的,往往出现各种借机器等机器,一个验证只要十分钟的问题却花几个小时找机器,实在不划算。

cpu和厂商相关的,只能依赖于手机这个不谈。

先来介绍一下wm,因为代码不多所以下面全贴出了。

/*
**
** Copyright 2013, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/


package com.android.commands.wm;

import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.AndroidException;
import android.view.Display;
import android.view.IWindowManager;
import com.android.internal.os.BaseCommand;

import java.io.PrintStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Wm extends BaseCommand {

    private IWindowManager mWm;

    /**
     * Command-line entry point.
     *
     * @param args The command-line arguments
     */
    public static void main(String[] args) {
        (new Wm()).run(args);
    }

    public void onShowUsage(PrintStream out) {
        out.println(
                "usage: wm [subcommand] [options]\n" +
                "       wm size [reset|WxH]\n" +
                "       wm density [reset|DENSITY]\n" +
                "       wm overscan [reset|LEFT,TOP,RIGHT,BOTTOM]\n" +
                "\n" +
                "wm size: return or override display size.\n" +
                "\n" +
                "wm density: override display density.\n" +
                "\n" +
                "wm overscan: set overscan area for display.\n"
                );
    }

    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("density")) {
            runDisplayDensity();
        } else if (op.equals("overscan")) {
            runDisplayOverscan();
        } else {
            showError("Error: unknown command '" + op + "'");
            return;
        }
    }

    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 = Integer.parseInt(wstr);
                h = Integer.parseInt(hstr);
            } catch (NumberFormatException e) {
                System.err.println("Error: bad number " + e);
                return;
            }
        }

        try {
            if (w >= 0 && h >= 0) {
                // TODO(multidisplay): For now Configuration only applies to main screen.
                mWm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, w, h);
            } else {
                mWm.clearForcedDisplaySize(Display.DEFAULT_DISPLAY);
            }
        } catch (RemoteException e) {
        }
    }

    private void runDisplayDensity() throws Exception {
        String densityStr = nextArg();
        int density;
        if (densityStr == null) {
            try {
                int initialDensity = mWm.getInitialDisplayDensity(Display.DEFAULT_DISPLAY);
                int baseDensity = mWm.getBaseDisplayDensity(Display.DEFAULT_DISPLAY);
                System.out.println("Physical density: " + initialDensity);
                if (initialDensity != baseDensity) {
                    System.out.println("Override density: " + baseDensity);
                }
            } catch (RemoteException e) {
            }
            return;
        } else if ("reset".equals(densityStr)) {
            density = -1;
        } else {
            try {
                density = Integer.parseInt(densityStr);
            } catch (NumberFormatException e) {
                System.err.println("Error: bad number " + e);
                return;
            }
            if (density < 72) {
                System.err.println("Error: density must be >= 72");
                return;
            }
        }

        try {
            if (density > 0) {
                // TODO(multidisplay): For now Configuration only applies to main screen.
                mWm.setForcedDisplayDensity(Display.DEFAULT_DISPLAY, density);
            } else {
                mWm.clearForcedDisplayDensity(Display.DEFAULT_DISPLAY);
            }
        } catch (RemoteException e) {
        }
    }

    private void runDisplayOverscan() throws Exception {
        String overscanStr = nextArgRequired();
        Rect rect = new Rect();
        int density;
        if ("reset".equals(overscanStr)) {
            rect.set(0, 0, 0, 0);
        } else {
            final Pattern FLATTENED_PATTERN = Pattern.compile(
                    "(-?\\d+),(-?\\d+),(-?\\d+),(-?\\d+)");
            Matcher matcher = FLATTENED_PATTERN.matcher(overscanStr);
            if (!matcher.matches()) {
                System.err.println("Error: bad rectangle arg: " + overscanStr);
                return;
            }
            rect.left = Integer.parseInt(matcher.group(1));
            rect.top = Integer.parseInt(matcher.group(2));
            rect.right = Integer.parseInt(matcher.group(3));
            rect.bottom = Integer.parseInt(matcher.group(4));
        }

        try {
            mWm.setOverscan(Display.DEFAULT_DISPLAY, rect.left, rect.top, rect.right, rect.bottom);
        } catch (RemoteException e) {
        }
    }
}

其中这段简单的介绍了如何使用wm

public void onShowUsage(PrintStream out) {
        out.println(
                "usage: wm [subcommand] [options]\n" +
                "       wm size [reset|WxH]\n" +
                "       wm density [reset|DENSITY]\n" +
                "       wm overscan [reset|LEFT,TOP,RIGHT,BOTTOM]\n" +
                "\n" +
                "wm size: return or override display size.\n" +
                "\n" +
                "wm density: override display density.\n" +
                "\n" +
                "wm overscan: set overscan area for display.\n"
                );
    }

可以看出wm大概提供了几样基本功能:

  • wm size:返回当前屏幕的分辨率,单纯运行wm size命令将会得到lcd本身设置的显示分辨率。
    wm size W x H命令是按witch x hight 设置分辨率。
    wm size reset 命令是将分辨率设置为原始分辨率。
C02PC5DQFVH5:~ rodmanliu$ adb shell wm size
Physical size: 1080x1920
  • wm density:该命令的用法类似于wm size 命令,作用是读取、设置或者重置屏幕的density值

  • wm overscan [reset|LEFT,TOP,RIGHT,BOTTOM]

该命令用来设置、重置LCD的显示区域。四个参数分别是显示边缘距离LCD左、上、右、下的像素数。例如,对于分辨率为540x960的屏幕,通过执行 命令wm overscan 0,0,0,420可将显示区域限定在一个540x540的矩形框里。了解wm可以解决LCD图标大小显示不正常的问题。但是这些设置都是临时的,适合于调试来确定问题和解决办法。

不过还是有一个问题,有些手机上没有封装wm类,你再使用时会遇到如下提示:

C02PC5DQFVH5:~ rodmanliu$ adb shell
root@android:/ # vm size
/system/bin/sh: vm: not found

还好这时我们可以招到另外一种方法adb shell am display-size
功效是接近的。修改分辨率可以如下操作

adb shell am display-size 720x1280 \\修改分辨率为720x1280

adb shell am display-size reset     \\恢复默认设置

后面的分辨率最好不要超过屏幕本身分辨率,否则可能会超出屏幕(当然了我说的是可能)。

如下,启动cmd,查看当前分辨率,可以看到当前的分辨率是768x1280

C02PC5DQFVH5:~ rodmanliu$ adb shell dumpsys window -w | grep cur=
init=768x1280 320dpi cur=768x1280 app=768x1184 rng=768x718-1196x1134

尝试修改分辨率640x960,如下图可见修改后,查看当前分辨率,cur一项已经能看到是640x960

C02PC5DQFVH5:~ rodmanliu$ adb shell am display-size 640x960
C02PC5DQFVH5:~ rodmanliu$ adb shell dumpsys window -w | grep cur=
init=768x1280 320dpi base=640x960 320dpi cur=640x960 app=640x864 rng=640x590-876x814

为了方便大家不用每次都在电脑上操作,crashdigger1.2集成了分辨率修改的功能。

打开crashdigger,切换到分辨率tab,点击"开始测试"

此处输入图片的描述
此处输入图片的描述

注意,因为该操作需要root权限,所以要确定你的手机已经root,如果没有root将无法进度下面功能

此处输入图片的描述
此处输入图片的描述

如上点击你需要切换的分辨率,开始做分辨率兼容性测试吧

以下是一账通切换小分辨率时候状况,还是不少问题的

此处输入图片的描述
此处输入图片的描述

比如上图,文字都立起来了。

此处输入图片的描述
此处输入图片的描述

比如这个,内容已经堆叠在了一起。

如果你的手机比较ok,还可以直接切换很高的分辨率,比如1440x2560. 手机瞬间变pad有没有。这下连超大分辨率的适配也做了,省去好几千买新机器的钱呢。

此处输入图片的描述
此处输入图片的描述
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,409评论 25 707
  • 想起和杨诗音分手那天。 去中寨找她,死活不相见。 电话里头,她说:“不论以后我找个什么样的,只要我父母高兴,只要我...
    山南有竹阅读 245评论 0 0
  • ——乡村医生潘月勇 五莲县许孟镇西楼子村,坐落在许孟镇,西葫芦之乡。这...
    六九贺英杰阅读 535评论 0 1
  • 在这个“世态炎凉,人情淡泊”的时代,以结果为导向,以目标结果的达成情况作为价值衡量的标准,已经成为社会群体的共识,...
    凡提阅读 426评论 0 0
  • 路由规则和分组支持绑定模型数据,例如: 会自动给当前路由绑定 id为 当前路由变量值的User模型数据。就是自动读...
    呦丶耍脾气阅读 3,305评论 1 0