1. 程式人生 > >【Android】利用 ACRA 實現在規定時間內崩潰次數超過規定值就自動清理 APP 資料

【Android】利用 ACRA 實現在規定時間內崩潰次數超過規定值就自動清理 APP 資料

其實 ACRA 是一個崩潰處理的類庫,其功能就是收集App崩潰堆疊資訊,生成報告併發送到指定端,當然它也可以自己定製對應的操作,所以是個處理崩潰很不錯的庫。

ACRA Application Crash Reports for Android

其實在規定時間內崩潰次數超過規定值就自動清理 APP 資料這個功能在一些大型APP上都會有,因為這對於解決某些因本地資料導致的崩潰有很好的作用,使用者沒必要再進行解除安裝重灌,算是一個細節加分。

本文實現的是清理 APP 自身 data 目錄下的資料以及 SharedPreferences 裡面的資料,如需清理其它目錄下的資料請參考程式碼進行修改。

下面就說說怎麼去實現:

首先我們先在 build.gradle 裡面新增依賴:

dependencies {

    ...
    
    compile 'ch.acra:acra:4.7.0'
    
    ...
}

這裡要宣告一下為什麼要用4.7.0,寫這篇文章的時候最新的版本是4.9.2,其實幾個版本都試過,4.7.0最合我心意,因為這個版本在 Application 初始化資料崩潰也會被收集,4.7.0之後就不行,所以為了多一層保障而選擇了4.7.0。

接下來是自定義 Application:

@ReportsCrashes(
//一些ACRA的設定,具體參考ACRA文件,因為我們使用自定義Sender,所以這裡完全可以不用設定
// mailTo = "
[email protected]
", // mode = ReportingInteractionMode.TOAST, // resToastText = R.string.crash_toast_text ) public class MyApplication extends Application { private static Context context; @Override public void onCreate() { initACRA(); super.onCreate(); context = getApplicationContext(); } public void initACRA() { if (!BuildConfig.DEBUG) { //這裡判斷只有在非DEBUG下才清除資料,主要是為了在開發過程中能夠保留執行緒。 ACRA.init(this); CrashHandler handler = new CrashHandler(); ACRA.getErrorReporter().setReportSender(handler); //在閃退時檢查是否要清空資料 } } public static Context getContext(){ return context; } }

然後在 AndroidManifest.xml 裡面設定 Application:
<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    package="...">  
    <application  
        ...  
        android:name="xxx.MyApplication">  
        ...  
    </application>  
</manifest> 

接下來就是自定義 ReportSender 類的程式碼:
public class CrashHandler implements ReportSender {
    @Override
    public void send(Context context, CrashReportData errorContent) throws ReportSenderException {
        //閃退,檢查是否需要清空資料
        new CrashModel().checkAndClearData();
    }
}

然後是崩潰處理 CrashModel 類的程式碼:
public class CrashModel {
    private static final String CRASH_TIME_FILE_NAME = "crash_time";
    //不能通過App.getPackageName來獲取包名,否則會有問題,只能預設為cn.campusapp.campus。所以對於debug或者運營版本,清資料會把release的清掉
    private static final String FILE_DIR = String.format("/data/data/%s/", BuildConfig.APPLICATION_ID);

    protected ArrayList<Long> mCrashTimes;
    Gson gson = new Gson();

    public CrashModel() {
        mCrashTimes = readCrashTimes();
        if (mCrashTimes == null) {
            mCrashTimes = new ArrayList<>();
            storeCrashTimes(mCrashTimes);
        }
    }


    public void checkAndClearData() {
        long timeNow = System.currentTimeMillis();

        if (checkClearData(timeNow, new ArrayList<>(mCrashTimes))) {
            //已經在5分鐘之內有三次閃退,需要清理資料
            try {
                clearData();
            }
            catch (Exception e) {
                //清空所有資料失敗
            }
        }
        else {
            mCrashTimes.add(timeNow);
            storeCrashTimes(mCrashTimes);
            //此次不需要清空資料, 崩潰此時:gson.toJson(mCrashTimes));
        }
    }

    private void storeCrashTimes(ArrayList<Long> crashTimes) {
        try {
            String str = gson.toJson(crashTimes);
            FileUtil.writeToFile(FILE_DIR + CRASH_TIME_FILE_NAME, str);
        }
        catch (Exception e) {
            //儲存閃退時間失敗
        }

    }

    private ArrayList<Long> readCrashTimes() {
        try {
            String timeStr = FileUtil.readFileContent(FILE_DIR + CRASH_TIME_FILE_NAME);
            return gson.fromJson(timeStr, new TypeToken<ArrayList<Long>>() {
            }.getType());
        }
        catch (Exception e) {
            //讀取閃退時間失敗
        }
        return null;
    }

    /**
     * 檢查是否需要清空資料,目前的清空策略是在5分鐘之內有三次閃退的就清空資料,也就是從後往前遍歷,只要前兩次閃退發生在5分鐘之內,就清空資料
     *
     * @return
     */
    private boolean checkClearData(long time, ArrayList<Long> crashTimes) {
        //Timber.i(gson.toJson(crashTimes));
        int count = 0;
        for (int i = crashTimes.size() - 1; i >= 0; i--) {
            long crashTime = crashTimes.get(i);
            if (time - crashTime <= 5 * 60 * 1000) {
                count++;
                if (count >= 2) {
                    break;
                }
            }
        }
        if (count >= 2) {
            //在5分鐘之內有三次閃退,這時候需要清空資料
            return true;
        } else {
            return false;
        }
    }

    /**
     * 清空資料,包括資料庫中的和SharedPreferences中的
     *
     * @throws Exception
     */
    private void clearData() throws Exception {
        //開始清理資料
        FileUtil.delFolderNew(FILE_DIR);
        SharedPreUtil.getInstance().Clear();
    }
}

這裡需要用到 Gson ,請從 GitHub 獲取 jar:

然後還有一個檔案處理類 FileUtil:

public class FileUtil {
	/**
     * Prints some data to a file using a BufferedWriter
     */
	public static boolean writeToFile(String filename, String data) {
		BufferedWriter bufferedWriter = null;
		try {
			// Construct the BufferedWriter object
			bufferedWriter = new BufferedWriter(new FileWriter(filename));
			// Start writing to the output stream
			bufferedWriter.write(data);
			return true;
		} catch (FileNotFoundException ex) {
			ex.printStackTrace();
		} catch (IOException ex) {
			ex.printStackTrace();
		} finally {
			// Close the BufferedWriter
			try {
				if (bufferedWriter != null) {
					bufferedWriter.flush();
					bufferedWriter.close();
				}
			} catch (IOException ex) {
				ex.printStackTrace();
			}
		}
		return false;
	}

	/**
	 * 讀檔案,並返回String
	 * @param strFileName 檔名
	 */
	public static String readFileContent(String strFileName) throws IOException {
		File file = new File(strFileName);
		return readFileContentAsString(file, null);
	}


	public static String readFileContentAsString(File file, String charsetName) throws IOException {
		if (!file.exists() || !file.isFile()) {
			throw new IOException("File to be readed not exist, file path : " + file.getAbsolutePath());
		}

		FileInputStream fileIn = null;
		InputStreamReader inReader = null;
		BufferedReader bReader = null;
		try {
			fileIn = new FileInputStream(file);
			inReader = charsetName == null ? new InputStreamReader(fileIn) : new InputStreamReader(fileIn, charsetName);
			bReader = new BufferedReader(inReader);
			StringBuffer content = new StringBuffer();
			char[] chBuffer = new char[1024];
			int readedNum = -1;
			while ((readedNum = bReader.read(chBuffer)) != -1) {
				content.append(chBuffer, 0, readedNum);
			}

			return content.toString();
		} finally {
			if (fileIn != null) {
				try {
					fileIn.close();
				} catch (IOException e) {
				}
			}

			if (bReader != null) {
				try {
					bReader.close();
				} catch (IOException e) {
				}
			}
		}
	}


	/**
	 * 刪除資料夾(調整後的)
	 *
	 * @param folderPath
	 *            String 資料夾路徑及名稱 如c:/fqf
	 */
	public static void delFolderNew(String folderPath) {
		try {
			delAllFileNew(folderPath); // 刪除完裡面所有內容

			File myFilePath = new File(folderPath);
			myFilePath.delete(); // 刪除空資料夾

		}
		catch (Exception e) {
			System.out.println("刪除資料夾操作出錯");
			e.printStackTrace();
		}
	}

	/**
	 * 刪除資料夾裡面的所有檔案(調整後的)
	 * @param path String 資料夾路徑 如 c:/fqf
	 */
	public static void delAllFileNew(String path) {
		File file = new File(path);
		if (!file.exists()) {
			return;
		}
		if (!file.isDirectory()) {
			return;
		}

		String[] tempList = file.list();
		File temp = null;
		String tempPath = "";

		for (int i = 0; i < tempList.length; i++) {
			if (path.endsWith(File.separator)) {
				temp = new File(path + tempList[i]);
				tempPath = path + tempList[i];
			}
			else {
				temp = new File(path + File.separator + tempList[i]);
				tempPath = path + File.separator + tempList[i];
			}
			if (temp.isFile()) {
				temp.delete();
			}
			if (temp.isDirectory()) {
				delFolderNew(tempPath);// 先刪除資料夾裡面的檔案
			}
		}
	}
}
(檔案處理工具類很實用,這裡只展示了要用到的一部分,可以自己再豐富其方法。)

最後還有一個處理 SharedPreferences 資料的工具類:

public class SharedPreUtil{
    private SharedPreUtil() {}
    private static SharedPreUtil sharedpreutil;

    public static SharedPreUtil getInstance() {
        if (sharedpreutil == null) {
            synchronized (SharedPreUtil.class) {
                sharedpreutil = new SharedPreUtil();
            }
        }
        return sharedpreutil;
    }

    public static void clear(){
        SharedPreferences prefer = MyApplication.getContext().getSharedPreferences(PREFERENCE_NAME, MODE_PRIVATE);
        SharedPreferences.Editor editor = prefers.edit();

        editor.remove("key1");
        editor.remove("key2");
        editor.remove("key3");
    }
}

其思路就是通過 MyApplication 獲得 context,然後獲取 SharedPreferences,再進行資料處理。

這裡只是對崩潰次數進行判斷以及本地資料清理處理,如果想進行上報崩潰記錄等操作,可以再自行研究下 ACRA 這個庫。

相關推薦

Android利用 ACRA 實現規定時間崩潰次數超過規定值自動清理 APP 資料

其實 ACRA 是一個崩潰處理的類庫,其功能就是收集App崩潰堆疊資訊,生成報告併發送到指定端,當然它也可以自己定製對應的操作,所以是個處理崩潰很不錯的庫。 ACRA Application Crash Reports for Android 其實在規定時間內崩潰次數超

jQuery利用jQuery實現“記住我”的功能

jquer sms sep jquery實現 .com script lis put bar 【1】先下載jQuery.cookie插件:使用幫助請參考鏈接(https://github.com/carhartl/jquery-cookie)。 【2】安裝插件:

MATLAB 用 MATLAB 實現離散時間傅立葉變換(DTFT)的兩個案例分析

先給出離散時間傅立葉變換的簡單介紹: 如果 x(n) 是絕對可加的,即 那麼它的離散時間傅立葉變換給出為: w 稱為數字頻率,單位是每樣本 rad(弧度)或 (弧度/樣本)(rad/sam

Qt利用QAxObject實現word轉pdf

通過QAxObject類操作office的com元件操作word,呼叫word的介面儲存為pdf,所以必須安裝了office才能用。 下面先貼程式碼再做說明 QAxObject *pWordApplication = new QAxObject("Word.Appli

前端利用ajax實現偽檔案非同步上傳下載

利用ajax可以實現很酷的效果,在不重新整理頁面的情況下提交表單、修改資料狀態等等,可是如果表單裡還有input:file可就慘了,ajax不支援檔案的處理啊! ajax是使用了瀏覽器內部的XmlHttpRequest物件來傳輸XML資料的。既然是Xml的資料傳輸,那麼傳輸

android利用BuildConfig.DEBUG來控制日誌的輸出

前言 在Android 應用程式開發中,不可避免地會常常輸出各種除錯資訊,通常我們會使用android.util.Log類輸出該類日誌資訊(這是推薦使用的方式)。然而,在專案釋出的時候,我們常常需要關閉這些冗餘的Log資訊,手工關閉Log相當不方

WCF利用WCF實現上傳下載檔案服務

引言 前段時間,用WCF做了一個小專案,其中涉及到檔案的上傳下載。出於複習鞏固的目的,今天簡單梳理了一下,整理出來,下面展示如何一步步實現一個上傳下載的WCF服務。 服務端       1.首先新建一個名為FileService的WCF服務庫專案,如下圖:  

Android FragmentTabHost+Fragment實現多標籤頁

Android TabHost多標籤頁幾乎是所有APP中必備的控制元件,如通迅錄的【撥號、通話記錄、聯絡人】,微信的【聊天、聯絡人、發現】,如下圖 Android API13之後官方就不推薦使用TabActivity了,取而代之的是FragmentActivity+F

探索利用 canvas 實現資料壓縮

前言 HTTP 支援 GZip 壓縮,可節省不少傳輸資源。但遺憾的是,只有下載才有,上傳並不支援。如果上傳也能壓縮,那就完美了。特別適合大量文字提交的場合,比如部落格園,就是很好的例子。 雖然標準不支援「上傳壓縮」,但仍可以自己來實現。 Flash 首選方案當然是 Flash,畢竟它提供了壓縮 API。除了

Linux利用訊號實現sleep函式

在另一篇文章Linux訊號中,介紹了訊號的產生與處理方式,以及一系列訊號集函式的使用。 本文使用訊號機制,模擬實現sleep函式並瞭解競態條件。 在此之前先介紹一波需要用到的函式。 sigaction函式 #include <signal.h>

轉載vue.js實現格式化時間並每秒更新顯示功能示例

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user

IOS利用ASIHTTPRequest 實現一個簡單的登陸驗證

【原創作品, 歡迎轉載,轉載請在明顯處註明! 謝謝。 今天給大家帶來一個簡單的登陸驗證,用的是ASIHttpRequest 這個開源類庫,使用的方法很簡單,從網上下載下來以後,新增到專案中,並新增一下這些框架。 下面上程式碼 // // ViewControll

Android使用BaseAdapter實現複雜的ListView

步驟 使用BaseAdapter實現複雜的ListView的步驟: 1. 資料你要準備好 List getData()。 2. 繼承ListActivity專有屏,不再需要setContentView(xxx)。  3. 建立一個繼承自Ba

Android用RecycleView實現可以橫向滾動的ListView效果

終於閒下來了,總結一下RecycleView的使用。 一、概述 與常見的ListView和GridView一樣,RecycleView也用來在有限的介面上展示大量的資料。它提供了一種插拔式的體驗,高度的解耦,使用非常靈活,可以通過support-v7包進行匯入。先看以下Re

Android動態載入實現的簡單demo

概念▪說明動態載入:此處的動態載入是指從服務端或者其他地方獲取jar包,並在執行時期,載入jar包,並與 jar包互相呼叫。本例中,為了方便演示,將要動態載入的jar放到了assets目錄下,在程式執行時期,將其載入到/data/data/pkgname/files下,來模擬

Redis利用 Redis 實現分散式鎖

## 技術背景 首先我們需要先來了解下什麼是分散式鎖,以及為什麼需要分散式鎖。 對於這個問題,我們可以簡單將鎖分為兩種——記憶體級鎖以及分散式鎖,記憶體級鎖即我們在 Java 中的 synchronized 關鍵字(或許加上程序級鎖修飾更恰當些),而分散式鎖則是應用在分散式系統中的一種鎖機制。分散式鎖的應

Java 劍指offer(39) 陣列中出現次數超過一半的數字 《劍指Offer》Java實現合集 《劍指Offer》Java實現合集

本文參考自《劍指offer》一書,程式碼採用Java語言。 更多:《劍指Offer》Java實現合集   題目    陣列中有一個數字出現的次數超過陣列長度的一半,請找出這個數字。例如輸入一個長度為9的陣列{1, 2, 3, 2, 2, 2, 5, 4, 2}。由於數字2在陣列中出現

AndroidAndroid聊天機器人實現

小米 div bottom 曾經 圖靈 .9.png sdn http 歡迎界面 昨天看到一個Android視頻教程講圖靈機器人。那個API接口用起來還是挺方便的,就準備自己動手做一個了。另外自己還使用了高德地圖的API接口用於定位(曾經用過高德的接口,比X度方便) 大

Android實現線程異步小技巧

使用 msg xtend util rri wsh ride 執行 java 方式不止一種,這裏使用的是Timer類,創建一個定時器。我們經常需要獲得移動設備端口的顯示屏信息,但是onCreate()方法執行的時候,OnShow()方法不一定執行了,也就是說,在執行Oncr

Android如何實現Android發送短信

ted param close ase find array 短信 red phone 第一種:調用系統短信接口直接發送短信;主要代碼如下: /** * 直接調用短信接口發短信 * @param phoneNumber * @