• Android严苛模式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安全技术大本营

  • 相关阅读:
    什么是超参数
    KNN算法(K近邻算法)实现与剖析
    pandas中na_values与keep_default_na
    一篇文章搞懂python2、3编码
    深度学习基础篇之逻辑回归拟合二维数据
    采集万方医药方向的期刊+文章+作者信息(数据量千万级)
    win10安装tensorflow (cpu版)
    内存文件的读写
    海康威视面试python后端题
    Scrapy 采集需要登录注册的网站
  • 原文地址:https://www.cnblogs.com/yaowen/p/6024690.html
Copyright © 2020-2023  润新知