Android严苛模式StrictMode使用详解「建议收藏」

Android严苛模式StrictMode使用详解「建议收藏」StrictMode类是Android2.3(API9)引入的一个工具类,可以用来帮助开发者发现代码中的一些不规范的问题,以达到提升应用响应能力的目的。举个例子来说,如果开发者在UI线程中进行了网络操作或者文件系统的操作,而这些缓慢的操作会严重影响应用的响应能力,甚至出现ANR对话框。为了在开发中发现这些容易忽略的问题,我们使用StrictMode,系统检测出主线程违例的情况并做出相应的反应,

大家好,又见面了,我是你们的朋友全栈君。

       StrictMode类是Android 2.3 (API 9)引入的一个工具类,可以用来帮助开发者发现代码中的一些不规范的问题,以达到提升应用响应能力的目的。举个例子来说,如果开发者在UI线程中进行了网络操作或者文件系统的操作,而这些缓慢的操作会严重影响应用的响应能力,甚至出现ANR对话框。为了在开发中发现这些容易忽略的问题,我们使用StrictMode,系统检测出主线程违例的情况并做出相应的反应,最终帮助开发者优化和改善代码逻辑。

       官网文档:http://developer.android.com/reference/android/os/StrictMode.html

StrictMode具体能检测什么

严苛模式主要检测两大问题,一个是线程策略,即TreadPolicy,另一个是VM策略,即VmPolicy。

ThreadPolicy线程策略检测

  • 线程策略检测的内容有
  • 自定义的耗时调用 使用detectCustomSlowCalls()开启
  • 磁盘读取操作 使用detectDiskReads()开启
  • 磁盘写入操作 使用detectDiskWrites()开启
  • 网络操作 使用detectNetwork()开启

VmPolicy虚拟机策略检测

  • Activity泄露 使用detectActivityLeaks()开启
  • 未关闭的Closable对象泄露 使用detectLeakedClosableObjects()开启
  • 泄露的Sqlite对象 使用detectLeakedSqlLiteObjects()开启
  • 检测实例数量 使用setClassInstanceLimit()开启

工作原理

       其实StrictMode实现原理也比较简单,以IO操作为例,主要是通过在open,read,write,close时进行监控。libcore.io.BlockGuardOs文件就是监控的地方。以open为例,如下进行监控。

@Override
public FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
  BlockGuard.getThreadPolicy().onReadFromDisk();
    if ((mode & O_ACCMODE) != O_RDONLY) {
      BlockGuard.getThreadPolicy().onWriteToDisk();
    }
    return os.open(path, flags, mode);
}

其中onReadFromDisk()方法的实现,代码位于StrictMode.java中。

public void onReadFromDisk() {
    if ((mPolicyMask & DETECT_DISK_READ) == 0) {
      return;
    }
    if (tooManyViolationsThisLoop()) {
      return;
    }
    BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask);
    e.fillInStackTrace();
    startHandlingViolationException(e);
}

常见用法

       严格模式的开启可以放在Application或者Activity以及其他组件的onCreate方法。为了更好地分析应用中的问题,建议放在Application的onCreate方法中。
       其中,我们只需要在app的开发版本下使用 StrictMode,线上版本避免使用 StrictMode,这里定义了一个布尔值变量DEV_MODE来进行控制。

private boolean DEV_MODE = true;
 public void onCreate() {
     if (DEV_MODE) {
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                 .detectCustomSlowCalls() //API等级11,使用StrictMode.noteSlowCode
                 .detectDiskReads()
                 .detectDiskWrites()
                 .detectNetwork()   // or .detectAll() for all detectable problems
                 .penaltyDialog() //弹出违规提示对话框
                 .penaltyLog() //在Logcat 中打印违规异常信息
                 .penaltyFlashScreen() //API等级11
                 .build());
         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                 .detectLeakedSqlLiteObjects()
                 .detectLeakedClosableObjects() //API等级11
                 .penaltyLog()
                 .penaltyDeath()
                 .build());
     }
     super.onCreate();
 }

       其中Android3.0引入的方法包括detectCustomSlowCalls()和noteSlowCode(),它们都是用来检测应用中执行缓慢代码的或者潜在的缓慢代码。

查看报告结果

       严格模式有很多种报告违例的形式,但是想要分析具体违例情况,还是需要查看日志,终端下过滤StrictMode就能得到违例的具体stacktrace信息。

adb logcat | grep StrictMode

这里写图片描述

当然也可以选择弹窗形式来简明提醒开发者

弹窗警告

ThreadPolicy 详解

StrictMode.ThreadPolicy.Builder 主要方法如下

  • detectNetwork() 用于检查UI线程中是否有网络请求操作

检测UI线程中网络请求案例:

public class MainActivity extends AppCompatActivity implements View.OnClickListener { 
   

    Button btnTest;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                .detectNetwork()
                .penaltyLog()
                .build());
        btnTest = (Button) findViewById(R.id.btn_test);
        btnTest.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        switch (id) {
            case R.id.btn_test:
                postNetwork();
            break;
        }
    }

    /** * 网络连接的操作 */
    private void postNetwork() {
        try {
            URL url = new URL("http://www.wooyun.org");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.connect();
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    conn.getInputStream()));
            String lines = null;
            StringBuffer sb = new StringBuffer();
            while ((lines = reader.readLine()) != null) {
                sb.append(lines);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行后,触发的警告如下

这里写图片描述

  • detectDiskReads() 和 detectDiskWrites() 是磁盘读写检查

磁盘读写检查案例:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { 
   

    Button btnTest;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                .detectDiskWrites()
                .detectDiskReads()
                .penaltyLog()
                .build());
        btnTest = (Button) findViewById(R.id.btn_test);
        btnTest.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        switch (id) {
            case R.id.btn_test:
                writeToExternalStorage();
                break;
        }
    }

    /** * 文件系统的操作 */
    public void writeToExternalStorage() {
        File externalStorage = Environment.getExternalStorageDirectory();
        File mbFile = new File(externalStorage, "castiel.txt");
        try {
            OutputStream output = new FileOutputStream(mbFile, true);
            output.write("www.wooyun.org".getBytes());
            output.flush();
            output.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行后,触发的警告如下
这里写图片描述

  • noteSlowCall针对执行比较耗时的检查

           StrictMode从 API 11开始允许开发者自定义一些耗时调用违例,这种自定义适用于自定义的任务执行类中,比如我们有一个进行任务处理的类,为TaskExecutor。

public class TaskExecutor {    public void execute(Runnable task) {        task.run();    }}

       先需要跟踪每个任务的耗时情况,如果大于500毫秒需要提示给开发者,noteSlowCall就可以实现这个功能,如下修改代码

public class TaskExecutor {

    private static long SLOW_CALL_THRESHOLD = 500;
    public void executeTask(Runnable task) {
        long startTime = SystemClock.uptimeMillis();
        task.run();
        long cost = SystemClock.uptimeMillis() - startTime;
        if (cost > SLOW_CALL_THRESHOLD) {
            StrictMode.noteSlowCall("slowCall cost=" + cost);
        }
    }
}

执行一个耗时2000毫秒的任务

TaskExecutor executor = new TaskExecutor();
executor.executeTask(new Runnable() {
  @Override
    public void run() {
        try {
          Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

       得到的违例日志,注意其中~duration=20 ms并非耗时任务的执行时间,而我们的自定义信息msg=slowCall cost=2000才包含了真正的耗时。

  • penaltyDeath(),当触发违规条件时,直接Crash掉当前应用程序。

  • penaltyDeathOnNetwork(),当触发网络违规时,Crash掉当前应用程序。

  • penaltyDialog(),触发违规时,显示对违规信息对话框。

  • penaltyFlashScreen(),会造成屏幕闪烁,不过一般的设备可能没有这个功能。

  • penaltyDropBox(),将违规信息记录到 dropbox 系统日志目录中(/data/system/dropbox),你可以通过如下命令进行插件:

adb shell dumpsys dropbox dataappstrictmode  --print
  • permitCustomSlowCalls()、permitDiskReads ()、permitDiskWrites()、permitNetwork: 如果你想关闭某一项检测,可以使用对应的permit*方法。

VMPolicy 详解

StrictMode.VmPolicy.Builder 主要方法如下

  • detectActivityLeaks() 用户检查 Activity 的内存泄露情况

内存泄露检查案例:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { 
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                .detectActivityLeaks()
                .penaltyLog()
                .build()
        );

        new Thread() {
            @Override
            public void run() {
                while (true) {

                    SystemClock.sleep(1000);
                }
            }
        }.start();

    }
}

我们反复旋转屏幕就会输出提示信息(重点在 instances=2; limit=1 这一行)
这里写图片描述
       这时因为,我们在Activity中创建了一个Thread匿名内部类,而匿名内部类隐式持有外部类的引用。而每次旋转屏幕是,Android会新创建一个Activity,而原来的Activity实例又被我们启动的匿名内部类线程持有,所以不会释放,从日志上看,当先系统中该Activty有4个实例,而限制是只能创建1各实例。我们不断翻转屏幕,instances 的个数还会持续增加。

  • detectLeakedClosableObjects()用于资源没有正确关闭时提醒

// 资源引用没有关闭检查案例
public class MainActivity extends AppCompatActivity implements View.OnClickListener { 
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                .detectLeakedClosableObjects()
                .penaltyLog()
                .build()
        );

        File newxmlfile = new File(Environment.getExternalStorageDirectory(), "castiel.txt");
        try {
            newxmlfile.createNewFile();
            FileWriter fw = new FileWriter(newxmlfile);
            fw.write("猴子搬来的救兵WooYun");
            //fw.close(); 我们在这里特意没有关闭 fw
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行后触发警告如下
这里写图片描述

  • detectLeakedSqlLiteObjects() 和
    detectLeakedClosableObjects()的用法类似,只不过是用来检查 SQLiteCursor 或者 其他 SQLite
    对象是否被正确关闭

  • detectLeakedRegistrationObjects() 用来检查 BroadcastReceiver 或者
    ServiceConnection 注册类对象是否被正确释放

  • setClassInstanceLimit(),设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露

检测内存泄露案例
public class MainActivity extends AppCompatActivity implements View.OnClickListener { 
   

    private static class CastielClass{ 
   }
    private static List<CastielClass> classList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        classList = new ArrayList<CastielClass>();

        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                .setClassInstanceLimit(CastielClass.class, 2)
                .penaltyLog()
                .build());

        classList.add(new CastielClass());
        classList.add(new CastielClass());
        classList.add(new CastielClass());
        classList.add(new CastielClass());
        classList.add(new CastielClass());
        classList.add(new CastielClass());
        classList.add(new CastielClass());
        classList.add(new CastielClass());

    }
}

运行后触发警告如下

这里写图片描述

其他操作

       除了通过日志查看之外,我们也可以在开发者选项中开启严格模式,开启之后,如果主线程中有执行时间长的操作,屏幕则会闪烁,这是一个更加直接的方法。
这里写图片描述

注意事项

  • 只在开发阶段启用StrictMode,发布应用或者release版本一定要禁用它。
  • 严格模式无法监控JNI中的磁盘IO和网络请求。
  • 应用中并非需要解决全部的违例情况,比如有些IO操作必须在主线程中进行。

参考链接:http://droidyue.com/blog/2015/09/26/android-tuning-tool-strictmode/

安卓开发高级技术交流QQ群:108721298 欢迎入群

微信公众号:mobilesafehome

(本公众号支持投票)

Android安全技术大本营

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/136324.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • xpath定位随机元素之starts-with的用法

    xpath定位随机元素之starts-with的用法

    2021年5月24日
    117
  • java暗装没有快捷键,java强行videos

    java暗装没有快捷键,java强行videosjava强行videos“一会儿就回来了,强行关了又开更费电。”“但我很喜欢做。”然后她就更用力地抱紧他,小脸也贴到了他背后。小萌一号:强行“那你要照顾好自己和梨香哦,我会看好家的,早点回来吖!”“现在呢?”“……”燥热。听到沈郁要出门,强行白小萌也想跟着去的,但它只是一只懒惰的猫咪,现在舒服的很,爬起身来看了看,又扭头躺下做按摩了。反正沈郁每天晚上都和小萌睡,强行白天就让给梨香吧,白小萌觉得自己…

    2022年7月9日
    25
  • 请用决策树_cart决策树使用什么来选择划分属性

    请用决策树_cart决策树使用什么来选择划分属性CART,又名分类回归树,是在ID3的基础上进行优化的决策树,学习CART记住以下几个关键点:(1)CART既能是分类树,又能是分类树;(2)当CART是分类树时,采用GINI值作为节点分裂的依据;当

    2022年8月1日
    6
  • 在pycharm中使用tensorflow_使用中是什么意思

    在pycharm中使用tensorflow_使用中是什么意思QtDesigner的介绍在PyQt中编写UI界面可以直接通过代码来实现,也可以通过QtDesigner来完成。QtDesigner的设计符合MVC的架构,其实现了视图和逻辑的分离,从而实现了开发的便捷。QtDesigner中的操作方式十分灵活,其通过拖拽的方式放置控件可以随时查看控件效果。QtDesigner生成的.ui文件(实质上是XML格式的文件)也可以通过pyuic5工具转换成…

    2022年8月28日
    0
  • 关于面试总结11-selenium面试题[通俗易懂]

    关于面试总结11-selenium面试题[通俗易懂]前言面试web自动化必然会问到selenium,问selenium相关的问题定位是最基本的,也是自动化的根本,所以面试离不开元素定位问题。之前看到招聘要求里面说“只会复制粘贴xpath的就不要投简历了”,说明面试官对求职者的自动化能力要求不能停留在复制粘贴上。还是那句话,想学自动化的话,需牢记:录制穷三代,复制毁一生!1.如何判断一个页面上元素是否存在?这个可以说是被问烂的题了,判断元素存在方法有三种:方法一,用try…except…defis_element_exsist(driver,

    2022年6月20日
    17
  • 深究递归和迭代的区别、联系、优缺点及实例对比「建议收藏」

    深究递归和迭代的区别、联系、优缺点及实例对比「建议收藏」深究递归和迭代的区别、联系、优缺点及实例对比1.概念区分递归的基本概念:程序调用自身的编程技巧称为递归,是函数自己调用自己.一个函数在其定义中直接或间接调用自身的一种方法,它通常把一个大型的复杂的问题转化为一个与原问题相似的规模较小的问题来解决,可以极大的减少代码量.递归的能力在于用有限的语句来定义对象的无限集合.使用递归要注意的有两点:1)递归就是在过程或函数里面调用自身;

    2022年6月5日
    41

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号