android基礎知識---重寫系統Crash處理類保存上傳和完美退出程序的方法

當今市場上android的手機型號和版本太多要做到完全適配幾乎是完全不可能的,那麼怎麼才能獲取其他的玩家的出錯的信息呢!這裏我們就要重新定義系統的Crash處理類了。

首先我們我們新建一個CustomCrashHandler類 實現UncaughtExceptionHandler接口,重寫回調方法void uncaughtException(Thread thread, Throwable ex)

package com.example.admin.crashchuli;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

/**
 * 自定義系統的Crash捕捉類,用Toast替換系統的對話框
 * 將軟件版本信息,設備信息,出錯信息保存在sd卡中,方便上傳到服務器中
 * @author ls
 *
 */
public class CustomCrashHandler implements Thread.UncaughtExceptionHandler {
    private static final String TAG = "Activity";
    private Context mContext;
    private static final String SDCARD_ROOT = Environment.getExternalStorageDirectory().toString();
    private static CustomCrashHandler mInstance = new CustomCrashHandler();


    private CustomCrashHandler(){}
    /**
     * 單例模式,保證只有一個CustomCrashHandler實例存在
     * @return
     */
    public static CustomCrashHandler getInstance(){
        return mInstance;
    }

    /**
     * 異常發生時,系統回調的函數,我們在這裏處理一些操作
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        //將一些信息保存到SDcard中
        savaInfoToSD(mContext, ex);

        //提示用戶程序即將退出
        showToast(mContext, "很抱歉,程序遭遇異常,即將退出!");
        try {
            thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//      android.os.Process.killProcess(android.os.Process.myPid());
//        System.exit(1);

        //完美退出程序方法
        ExitAppUtils.getInstance().exit();

    }


    /**
     * 爲我們的應用程序設置自定義Crash處理
     */
    public void setCustomCrashHanler(Context context){
        mContext = context;
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    /**
     * 顯示提示信息,需要在線程中顯示Toast
     * @param context
     * @param msg
     */
    private void showToast(final Context context, final String msg){
        new Thread(new Runnable() {

            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }).start();
    }


    /**
     * 獲取一些簡單的信息,軟件版本,手機版本,型號等信息存放在HashMap中
     * @param context
     * @return
     */
    private HashMap<String, String> obtainSimpleInfo(Context context){
        HashMap<String, String> map = new HashMap<String, String>();
        PackageManager mPackageManager = context.getPackageManager();
        PackageInfo mPackageInfo = null;
        try {
            mPackageInfo = mPackageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        map.put("versionName", mPackageInfo.versionName);
        map.put("versionCode", "" + mPackageInfo.versionCode);

        map.put("MODEL", "" + Build.MODEL);
        map.put("SDK_INT", "" + Build.VERSION.SDK_INT);
        map.put("PRODUCT", "" +  Build.PRODUCT);

        return map;
    }


    /**
     * 獲取系統未捕捉的錯誤信息
     * @param throwable
     * @return
     */
    private String obtainExceptionInfo(Throwable throwable) {
        StringWriter mStringWriter = new StringWriter();
        PrintWriter mPrintWriter = new PrintWriter(mStringWriter);
        throwable.printStackTrace(mPrintWriter);
        mPrintWriter.close();

        Log.e(TAG, mStringWriter.toString());
        return mStringWriter.toString();
    }

    /**
     * 保存獲取的 軟件信息,設備信息和出錯信息保存在SDcard中
     * @param context
     * @param ex
     * @return
     */
    private String savaInfoToSD(Context context, Throwable ex){
        String fileName = null;
        StringBuffer sb = new StringBuffer();

        for (Map.Entry<String, String> entry : obtainSimpleInfo(context).entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key).append(" = ").append(value).append("\n");
        }

        sb.append(obtainExceptionInfo(ex));

        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
            File dir = new File(SDCARD_ROOT + File.separator + "crash" + File.separator);
            if(! dir.exists()){
                dir.mkdir();
            }

            try{
                fileName = dir.toString() + File.separator + paserTime(System.currentTimeMillis()) + ".log";
                FileOutputStream fos = new FileOutputStream(fileName);
                fos.write(sb.toString().getBytes());
                fos.flush();
                fos.close();
            }catch(Exception e){
                e.printStackTrace();
            }

        }

        return fileName;

    }


    /**
     * 將毫秒數轉換成yyyy-MM-dd-HH-mm-ss的格式
     * @param milliseconds
     * @return
     */
    private String paserTime(long milliseconds) {
        System.setProperty("user.timezone", "Asia/Shanghai");
        TimeZone tz = TimeZone.getTimeZone("Asia/Shanghai");
        TimeZone.setDefault(tz);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
        String times = format.format(new Date(milliseconds));

        return times;
    }
}

當然不忘記加權限

   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>

然後是就是在重寫的Application類中的onCreate()方法中爲它設置Crash處理類。

/**
 * Created by ls on 2017/4/30.
 */

public class MyApplication  extends Application{
    @Override
    public void onCreate() {
        super.onCreate();
        CustomCrashHandler mCustomCrashHandler = CustomCrashHandler.getInstance();
        mCustomCrashHandler.setCustomCrashHanler(getApplicationContext());
    }
}

記得在清單文件中設置application這個就不用說了吧

然後隨便寫個空指針異常


public class MainActivity extends BaseActivity {
    TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTextView.setText("crash");
    }
}

然後我們就可看到在我們制定的文件夾下面有我們要的log的txt文件,剩下我就不說了就是簡單的上傳到服務器、

然後是ExitAppUtils

public class ExitAppUtils {
    /**
     * 轉載Activity的容器
     */
    private List<Activity> mActivityList = new LinkedList<Activity>();
    private static ExitAppUtils instance = new ExitAppUtils();

    /**
     * 將構造函數私有化
     */
    private ExitAppUtils(){};

    /**
     * 獲取ExitAppUtils的實例,保證只有一個ExitAppUtils實例存在
     * @return
     */
    public static ExitAppUtils getInstance(){
        return instance;
    }


    /**
     * 添加Activity實例到mActivityList中,在onCreate()中調用
     * @param activity
     */
    public void addActivity(Activity activity){
        mActivityList.add(activity);
    }

    /**
     * 從容器中刪除多餘的Activity實例,在onDestroy()中調用
     * @param activity
     */
    public void delActivity(Activity activity){
        mActivityList.remove(activity);
    }


    /**
     * 退出程序的方法
     */
    public void exit(){
        for(Activity activity : mActivityList){
            activity.finish();
        }

        System.exit(0);
    }


}

對了說下ExitAppUtils就是爲了設置完美退出因爲異常的時候會閃幾次或者有點手機直接退出不了這肯定不行,所以就用ExitAppUtils來控制,當然不能所有activity都寫所以要寫在BaseActivity然後其他的繼承就好了

package com.example.admin.crashchuli;

import android.app.Activity;
import android.os.Bundle;

/**
 * Created by admin on 2017/4/30.
 */

public class BaseActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ExitAppUtils.getInstance().addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ExitAppUtils.getInstance().delActivity(this);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章