Android異常捕獲篇(下)---retrofit實現檔案的上傳
阿新 • • 發佈:2019-01-10
前言
上篇說到我們將捕獲的crash日誌快取到了本地,那本篇我們將開始實現日誌的上傳。
需求
根據公司的需求,需要另起一個apk(公司產品比較特殊,不是移動裝置,不適用於手機app,不過上傳方法一樣),該應用不需要啟動只實現每次啟動公司應用或者切換網路時進行掃描上傳日誌,即通過接收廣播開啟service,實現方法前篇已講解過,連結:Android應用在安裝後未啟動的情況下無法收到開機等各類廣播
引導
假如本地共存了十份日誌,每次觸發上傳的時候會優先遍歷出最新的日誌,上傳成功即立即刪除進行下一個檔案上傳,上傳的時候千萬要注意一點,不能使用for迴圈直接遍歷上傳,這樣不僅可能會造成重複上傳(日誌還沒有刪除又被掃描出來重複上傳),還會給伺服器造成很大的壓力,本地也容易死迴圈,如圖(畫的有點醜,見諒了~)
實現步驟
1、在AndroidManifest.xml檔案中註冊廣播和Service,新增讀寫、網路許可權
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<receiver android:name=".receiver.LauNetBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="intent.action.START_UPLOAD_CRASH" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
<service android:name=".server.BootService"/>
2、建立廣播接收器來啟動service
public class LauNetBroadcastReceiver extends BroadcastReceiver {
private static boolean isActived = false;//用於過濾連續收到兩次網路連線上的通知
private static final String NET_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
private static final String LAUNCHER_ACTION = "intent.action.START_UPLOAD_CRASH";
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(NET_ACTION)) {
//檢查網路狀態的型別
int netWrokState = NetUtil.getNetWorkState(context);
switch (netWrokState) {
case 0:
//Log.i("TAG", "行動網路");
case 1:
//Log.i("TAG", "wifi網路");
if (!isActived) {
isActived = true;
Intent service = new Intent(context, BootService.class);
context.startService(service);
}
break;
case -1:
//Log.i("TAG", "沒有網路");
isActived = false;
break;
}
} else if (intent.getAction().equals(LAUNCHER_ACTION)) {
//Log.i("TAG", "接收到Launcher啟動廣播");
Intent service = new Intent(context, BootService.class);
context.startService(service);
}
}
}
3、建立Service實現檔案的上傳,因為裡面涉及到耗時操作,這裡採用IntentServie(只貼關鍵程式碼)
/**
* 遍歷檔案獲取最新檔案上傳
* <p>
* 上傳成功即刪除,失敗保留
*
* @param
*/
public synchronized void traverCrashLog() {
try {
final File file = new File(CONTENT_INFO_PATH);
if (file != null && file.isDirectory()) {
//獲取最新的日誌檔案
File fold = getNewFile(file);
if (fold != null) {
//讀取檔案內容
String content = readCrashContent(file.getAbsolutePath() + File.separator + fold.getName());
//網路請求上傳檔案
createRemoteImpl(content, fold);
}
}
} catch (Exception e) {
Log.e(TAG, "limitAppLogCount - " + e.getMessage());
}
}
先獲取到最新產生的日誌檔案
/**
* 每次遍歷一遍獲取最新的檔案
*/
public File getNewFile(File file) {
File[] files = file.listFiles(new CrashLogFliter());
File fold = null;
if (files != null && files.length > 0) {
Arrays.sort(files, comparator);
fold = files[files.length - 1];
return fold;
}
return null;
}
過濾與排序
/**
* 日誌檔案按修改時間排序
*/
private Comparator<File> comparator = new Comparator<File>() {
@Override
public int compare(File l, File r) {
if (l.lastModified() > r.lastModified())
return 1;
if (l.lastModified() < r.lastModified())
return -1;
return 0;
}
};
/**
* 過濾.log與crash開頭的檔案,防止掃描出正在寫入的檔案
*/
public class CrashLogFliter implements FileFilter {
@Override
public boolean accept(File file) {
if (file.getName().endsWith(".log") && file.getName().startsWith("crash"))
return true;
return false;
}
}
讀取crash日誌檔案內容
public synchronized String readCrashContent(String fileName) {
String content = "";
try {
File file = new File(fileName);
if (!file.exists()) {
return null;
}
FileInputStream fls = new FileInputStream(fileName);
byte[] data = new byte[fls.available()];
fls.read(data);
content = Base64.encodeToString(data, Base64.NO_WRAP);
fls.close();
} catch (IOException e) {
e.printStackTrace();
}
return content;
}
retrofit實現網路請求,上傳成功即刪除檔案,進行下一次的遍歷上傳
public void createRemoteImpl(String content, final File fold) {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(getDbManageUrl(getApplicationContext()))
.build();
final IUploadInterface repo = retrofit.create(IUploadInterface.class);
// Log.e("解碼上傳內容", new String(Base64.decode(content, Base64.NO_WRAP)));
Call<DataBean> call = repo.uploadData("crash", deviceId, content);
call.enqueue(new Callback<DataBean>() {
@Override
public void onResponse(Call<DataBean> call, Response<DataBean> response) {
// Log.i(TAG, "retrofit請求成功:" + new Gson().toJson(response.body()));
DataBean dataBean = response.body();
if (Integer.valueOf(dataBean.getMeta().getCode()) == 200) {
if (fold.exists()) {
fold.delete();
traverCrashLog();
}
}
}
@Override
public void onFailure(Call<DataBean> call, Throwable t) {
Log.e(TAG, "retrofit請求失敗:" + t.toString());
}
});
}
由於內容比較多,涉及到檔案遍歷和retrofit的使用,都貼出來會有點亂,如有需要可以留言發郵箱(包括儲存和上傳的完整demo),或者之後可能會直接上傳一份資源供大家參考~