StrictMode VMPolicy demo介绍

来源:互联网 发布:qq飞车曙光伯爵数据 编辑:程序博客网 时间:2024/05/20 20:02

1. 背景介绍:

在系统monkey测试和案例测试中,我们在dropbox发现了大量的关于strictmode严苛模式的报错,为了增强系统稳定性,我们打算在项目初期就把这些类型的报错提给开发,来解决。对此本文写了StrictMode(VmPolicy)类型的demo以供大家粗粗略了解StrictMode。

2. StrictMode 详解:

StrictMode 通过策略方式来让你自定义需要检查哪方面的问题。 主要有两中策略,一个时线程方策略- (ThreadPolicy),一个是VM方面的策略(VmPolicy)。
ThreadPolicy 主要用于发现在UI线程中是否有读写磁盘的操作,是否有网络操作,以及检查UI线程中调用的自定义代码是否执行得比较慢。
VmPolicy,主要用于发现内存问题,比如 Activity内存泄露, SQL 对象内存泄露, 资源未释放,能够限定某个类的最大对象数。

3. StrictMode(VmPolicy)默认检查的错误类型主要有以下五种可以自由组合:

.detectAll()
.detectActivityLeaks()
.detectFileUriExposure()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.detectLeakedSqlLiteObjects()

4.StrictMode(VmPolicy)Penalty 违规后的惩罚方式有以下三种可以自由组合:

.penaltyDropBox()
.penaltyDeath()
.penaltyLog()

5. 现写了上述检查的五种错误类型的demo,界面如下:

这里写图片描述

6. 下面介绍主要的代码:

import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.util.ArrayList;import java.util.List;import android.annotation.SuppressLint;import android.app.Activity;import android.content.ContentResolver;import android.content.ContentUris;import android.content.ContentValues;import android.content.Context;import android.content.IntentFilter;import android.database.Cursor;import android.net.Uri;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.Message;import android.os.StrictMode;import android.os.SystemClock;import android.provider.Contacts;import android.provider.ContactsContract;import android.provider.ContactsContract.Data;import android.provider.ContactsContract.RawContacts;import android.provider.ContactsContract.CommonDataKinds.Email;import android.provider.ContactsContract.CommonDataKinds.Phone;import android.provider.ContactsContract.CommonDataKinds.StructuredName;import android.text.TextUtils;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.TextView;@SuppressLint("HandlerLeak")@SuppressWarnings("deprecation")public class StrictModeDemo extends Activity implements View.OnClickListener {    private static final String TAG = "StrictModeActivity";    private static final boolean DEBUG_MODE = true;    private static final int MSG_GET_DATA_OK = 100;    private Context mContext;    private TextView mTextView;    private Button mCursorLeakButton;    private Button mClosableLeakButton;    private Button mActivityLeakButton;    private Button mRegistrationsLeakButton;    private Button mClassInstanceLimitButton;    private Thread mThread = null;    private TestStrictModeBroadcast mReceiver = null;    private List<ClassCounts> mClassList = new ArrayList<StrictModeDemo.ClassCounts>();    private static class ClassCounts{        /**No Content, Only used test!*/    }    private Handler mHandler = new Handler() {        @Override        public void handleMessage(Message msg) {            switch (msg.what) {                case MSG_GET_DATA_OK:                    String result = (String) msg.obj;                    if (TextUtils.isEmpty(result)) {                        result = "抱歉,手机没有联系人!";                    }                    mTextView.setText(result);                break;            }        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        //开启StrictMode模式        useVMStrictMode();        init();    }    @Override    protected void onDestroy() {       // unregisterReceiver(this.receiver);我们在这里故意没有释放掉receiver         mHandler.removeCallbacksAndMessages(null);        if (mThread != null && mThread.isAlive()) {            try {                mThread.stop();            } catch (Exception e) {}        }        super.onDestroy();    }    //使用线程,可能耗时    Runnable runnable = new Runnable() {        @Override        public void run() {            //检查cursorLeak,注册SQlite Cursor,没有调用close            String result = cursorLeakUncloseTest();            mHandler.sendMessage(mHandler.obtainMessage(MSG_GET_DATA_OK, result));        }    };    //初始化一些按键之类    private void init() {        mContext = this;        mTextView = (TextView) this.findViewById(R.id.content);        mCursorLeakButton = (Button)this.findViewById(R.id.cursorLeak);        mClosableLeakButton = (Button)this.findViewById(R.id.closableLeak);        mActivityLeakButton = (Button)this.findViewById(R.id.activityLeak);        mRegistrationsLeakButton = (Button)this.findViewById(R.id.RegistrationObjectsLeak);        mClassInstanceLimitButton = (Button)this.findViewById(R.id.setClassInstanceLimit);        mCursorLeakButton.setOnClickListener(this);        mClosableLeakButton.setOnClickListener(this);        mActivityLeakButton.setOnClickListener(this);        mRegistrationsLeakButton.setOnClickListener(this);        mClassInstanceLimitButton.setOnClickListener(this);    }    @Override    public void onClick(View v) {        switch (v.getId()) {        case R.id.cursorLeak:            Log.i(TAG,"----------->onClick");            mTextView.setText("正在读取联系人数据库,测试Cursor泄露,测试结果请查看: data/system/dropbox请等待...");            mThread = new Thread(runnable);            mThread.start();            break;        case R.id.closableLeak:            mTextView.setText("测试closableLeak,测试结果请查看data/system/dropbox");            File newxmlfile = new File(Environment.getExternalStorageDirectory(), "hello.txt");            try {                newxmlfile.createNewFile();                FileWriter fw = new FileWriter(newxmlfile);                fw.write("hellohellohello");                //fw.close(); 我们在这里故意没有关闭 fw            } catch (IOException e) {                e.printStackTrace();            }        break;        case R.id.activityLeak:            mTextView.setText("测试Activity泄漏,请点击物理Back退出应用或者旋转屏幕(由于严苛模式所以可能会异常崩溃),该项测试完毕后需要杀掉进程重新开启,测试结果请查看: data/system/dropbox");            new Thread() {                  @Override                  public void run() {                    while (true) {                           SystemClock.sleep(1000);                    }                  }            }.start();            break;        case R.id.RegistrationObjectsLeak:            mTextView.setText("测试RegistrationObjects泄露,检查BroadcastReceiver 或者 ServiceConnection 注册类对象是否被正确释放。测试结果请查看data/system/dropbox");            this.mReceiver = new TestStrictModeBroadcast();              IntentFilter filter = new IntentFilter();              filter.addAction("android.intent.action.STRICT_MODE");             registerReceiver(this.mReceiver, filter);             break;        case R.id.setClassInstanceLimit:            mTextView.setText("测试setClassInstanceLimit,设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露。测试结果请查看data/system/dropbox");            for (int index=0; index<8; index++) {                mClassList.add(new ClassCounts());            }            break;        }    }    //检查cursorLeak,注册SQlite Cursor,没有调用close    private String cursorLeakUncloseTest() {        StringBuilder builder = new StringBuilder("");        ContentResolver resolver = getContentResolver();        //插入20个联系人        for (int i = 1; i < 20; i++) {            insertContact("contact" + i, "email" + i, "123" + i);        }        //搜索联系人        Cursor cursor = resolver.query(Contacts.Phones.CONTENT_URI, null, null,null, null);        if (cursor != null) {            while (cursor.moveToNext()) {                String name = cursor.getString(cursor.getColumnIndex(Contacts.Phones.NAME));                builder.append(name);                builder.append("\n");            }            //cursor.close();我们在这里故意没有关闭 cursor        }        return builder.toString();    }    //开启StrictMode模式    private void useVMStrictMode() {        if(DEBUG_MODE){            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()            .detectAll()            .penaltyDropBox()            .penaltyDeath()            .penaltyLog()            .setClassInstanceLimit(ClassCounts.class, 2)            .build());          }    }    // 插入联系人    protected boolean insertContact(String given_name, String work_email,String mobile_number) {        try {            ContentValues values = new ContentValues();            Uri rawContactUri = getContentResolver().insert(RawContacts.CONTENT_URI, values);            long rawContactId = ContentUris.parseId(rawContactUri);            // 向data表插入姓名数据            if (given_name != "") {                values.clear();                values.put(Data.RAW_CONTACT_ID, rawContactId);                values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);                values.put(StructuredName.GIVEN_NAME, given_name);                getContentResolver().insert(ContactsContract.Data.CONTENT_URI,values);            }            if (mobile_number != null) {                values.clear();                values.put(Data.RAW_CONTACT_ID, rawContactId);                values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);                values.put(Phone.TYPE, Phone.TYPE_MOBILE);                values.put(Phone.NUMBER, mobile_number);                getContentResolver().insert(ContactsContract.Data.CONTENT_URI,values);            }            // 向data表插入Email数据            if (work_email != "") {                values.clear();                values.put(Data.RAW_CONTACT_ID, rawContactId);                values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);                values.put(Email.DATA, work_email);                values.put(Email.TYPE, Email.TYPE_WORK);                getContentResolver().insert(ContactsContract.Data.CONTENT_URI,values);            }        } catch (Exception e) {            e.printStackTrace();            return false;        }        return true;    }}

上面代码需要如下权限:

    android:name="android.permission.READ_CONTACTS" />    <uses-permission android:name="android.permission.WRITE_CONTACTS" />    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

上面就是整个测试Demo的主要代码,可以发现,主要是打开了严苛模式,然后模拟了上述背景介绍中存在的五种错误,譬如查询数据库用完Cursor后忘了关闭、注册广播后没有取消注册等,相对来说都是些比较常见却很容易漏写的错误。

7.Demo运行分析

有了上面Demo以后我们就可以运行程序进行严苛模式发现问题的模拟测试,下面我们主要以写入dropbox和log打印来简要介绍如何查看严苛模式探测的问题。
下面主要介绍下五种错误类型在dropbox里的log打印和解决方法。

7.1 点击第一个按钮->检查CursorLeak错误:

当点击Demo第一个按钮后由于我们在代码中模拟没有关闭Cursor,所以此时严苛模式会在dropbox中进行如下log打印:

System-App: falseUptime-Millis: 14075239//!!!!!!!!严苛模式最核心的关注点log!!!!!!!!java.lang.Throwable: Explicit termination method 'close' not called    at dalvik.system.CloseGuard.open(CloseGuard.java:184)    at java.io.FileOutputStream.<init>(FileOutputStream.java:89)    at java.io.FileOutputStream.<init>(FileOutputStream.java:72)    at java.io.FileWriter.<init>(FileWriter.java:42)    at com.meizu.strictmode.StrictModeActivity.onClick(StrictModeActivity.java:133)    at android.view.View.performClick(View.java:4851)    at android.view.View$PerformClick.run(View.java:20016)    at android.os.Handler.handleCallback(Handler.java:739)    at android.os.Handler.dispatchMessage(Handler.java:95)    at android.os.Looper.loop(Looper.java:135)    at android.app.ActivityThread.main(ActivityThread.java:5424)    at java.lang.reflect.Method.invoke(Native Method)    at java.lang.reflect.Method.invoke(Method.java:372)    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:947)    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:742)

通过上面Log就可以很容易发现问题所在点,不仅给出了潜在错误描述,还给出了问题出现的大致位置,这样一来就很容易定位问题,而不至于浪费很多时间去盲目查找,或者压根没发现自己忘了调close方法。
通过Log可以发现该类问题的解决方法就是成对出现,关闭Cursor;关闭后再此运行该问题不会再被严苛模式发现。
点击第二个按钮->检查closableLeak

7.2 当点击Demo第二个按钮时我们模拟了文件读写,但是没有调运colse方法的情况,此时会得到如下主要log(获取方法同上):

    java.lang.Throwable: Explicit termination method 'close' not called    at dalvik.system.CloseGuard.open(CloseGuard.java:184)    at java.io.FileOutputStream.<init>(FileOutputStream.java:89)    at java.io.FileOutputStream.<init>(FileOutputStream.java:72)    at java.io.FileWriter.<init>(FileWriter.java:42)    at com.meizu.strictmode.StrictModeActivity.onClick(StrictModeActivity.java:133)    at android.view.View.performClick(View.java:4851)    at android.view.View$PerformClick.run(View.java:20016)    at android.os.Handler.handleCallback(Handler.java:739)    at android.os.Handler.dispatchMessage(Handler.java:95)    at android.os.Looper.loop(Looper.java:135)    at android.app.ActivityThread.main(ActivityThread.java:5424)    at java.lang.reflect.Method.invoke(Native Method)    at java.lang.reflect.Method.invoke(Method.java:372)    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:947)    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:742)

可以发现,上面也给出了类似第一个按钮的错误Log,我们一样可以通过Log分析很快定位解决潜在的问题。

7.3 点击第三个按钮->检查activityLeak:

和上面两种类似,我们直接给出错误log,如下:

Instance-Count: 2android.os.StrictMode$InstanceCountViolation: class com.meizu.strictmode.StrictModeActivity; instances=2; limit=1at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)

可以看见,这类潜在错误出现后严苛模式一样可以给出相关信息,只是没有上面几种明显,但是最起码可以告诉我们哪个Activity存在内存泄漏,这已经帮忙我们缩小了问题范围,接下来我们就可以借助其他内存泄漏检测工具进行详细的定位分析解析即可。

7.4 点击第四个按钮->检查RegistrationObjectsLeak:

与上面类似,直接给出错误log,如下:

android.app.IntentReceiverLeaked: Activity com.meizu.strictmode.StrictModeActivity has leaked IntentReceiver com.meizu.strictmode.TestStrictModeBroadcast@345916f7 that was originally registered here. Are you missing a call to unregisterReceiver()?

分析方法与上面类似,不在累赘。

7.5 点击第五个按钮->检查setClassInstanceLimit :

同上,错误log如下:

android.os.StrictMode$InstanceCountViolation: class com.meizu.strictmode.StrictModeActivity$ClassCounts; instances=16; limit=2at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)   

可以看见,错误原因是开启了严苛模式,我们设置了一个类创建对象个数的上限,但却没有遵守,所以解决方案就是设置了一个类创建对象个数的上限后就不要超出上限。

8、严苛模式总结

有了上面的背景知识和Demo演示及Demo的问题Log分析后我们可以归纳总结如下:
严苛模式的意义:
我们平时写代码可能会由于一时疏忽等造成了代码存在潜在问题,但是我们代码又能正常执行,直观感觉没啥问题,所以就没再搭理,但是我们又不能保证终 端用户和我们一样用不出问题(譬如我们直接在UI线程查一个理论认为很快的东西,我们测试环境可能又没能覆盖到这种查询的极限情况—-一直查不到,终端用 户偶尔却有达到了这种极限,这种情况就是我们考虑疏忽导致功能ANR的潜在原因。),所以我们必须要想办法将代码尽量性能与稳定性达标,严苛模式的开启就 是让一些潜在问题暴漏在开发阶段的利器。
严苛模式的使用:
上面背景与Demo都有介绍,基本就是在开发阶段的代码中进行严苛模式的配置,然后勤看logcat打印与dropbox下的文件,发现一个消灭一个即可

1 0
原创粉丝点击