前言
上篇说到我们将捕获的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),或者之后可能会直接上传一份资源供大家参考~