一、概述
android应用捕捉所有线程没有catch的exception,并将相关日志文件压缩并发送到服务器。环境如下:
android: slf4j+logback,retrofit
server: play framework 2
二、自定义Application
public class MyApp extends Application {
private static final Logger log = LoggerFactory.getLogger(MyApp.class);
public boolean isUIThread(){
return Looper.getMainLooper().getThread() == Thread.currentThread();
}
@Override
public void onCreate() {
log.debug("onCreate");
super.onCreate();
//处理非捕捉异常
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable e) {
handleUncaughtException(thread, e);
}
});
}
private void handleUncaughtException(Thread thread, Throwable e) {
log.error("UncaughtException thread=" + thread.getName(), e);
if(isUIThread()) {//UI thread throw exception
invokeLogActivity();
}else{//non UI thread throw exception
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
invokeLogActivity();
}
});
}
}
//启动另外一个acitivy发送日志
private void invokeLogActivity(){
Intent intent = new Intent();
intent.setAction("cn.mydomain.SEND_LOG");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
System.exit(1);
}
}
三、发送错误日志文件的Activity
<activity
android:label="@string/base_warning"
android:name=".ui.SendErrorLogActivity"
android:theme="@android:style/Theme.Holo.Dialog" >
<intent-filter>
<action android:name="cn.mydomain.SEND_LOG" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
public class SendErrorLogActivity extends Activity{
private static final Logger log = LoggerFactory.getLogger(SendErrorLogActivity.class);
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setFinishOnTouchOutside(false);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(8, 8, 8, 8);
layout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
TextView textView = new TextView(this);
textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
textView.setText(R.string.error_exit);
textView.setTextAppearance(this, R.style.TextAppearance_AppCompat_Large);
textView.setPadding(8, 8, 8, 8);
layout.addView(textView);
button = new Button(this);
button.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
button.setText(R.string.base_confirm);
button.setGravity(Gravity.CENTER);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
log.info("send error file to server");
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_hh_mm_ss");
String targetFile = getCacheDir() + "/errorMsg" + sdf.format(new Date()) + ".gz";
CompressUtil.createGzFile(MyApp.instance.getAppDir() + "/files/log.txt", targetFile);//压缩文件
button.setEnabled(false);
//发送文件到服务器
MainManage.uploadErrorFile(new File(targetFile), new Function() {
@Override
public void success(Object o) {
log.error("send error file success");
log.error("app exit by uncaught exception");
System.exit(1);
}
@Override
public void fail(Throwable t) {
log.error("send error file fail", t);
log.error("app exit by uncaught exception");
System.exit(1);
}
});
} catch (Exception e1) {
log.error("send error file fail", e1);
log.error("app exit by uncaught exception");
System.exit(1);
}
}
});
layout.addView(button);
setContentView(layout);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
log.error("app exit by uncaught exception without send error file");
System.exit(1);
return true;
}
return super.onKeyDown(keyCode, event);
}
}
MainManage调用核心方法
public static void uploadErrorFile(File file, final Function callback){
log.info("uploadErrorFile");
service.uploadErrorFile(new TypedFile("application/x-gzip", file), new MyCallBack("uploadErrorFile fileName=" + file.getName(), callback));
}
public interface MyService{
@Multipart
@POST("/app/errorFile")
void uploadErrorFile(@Part("errorFile") TypedFile errorFile, Callback<Response> cb);
}
四、服务器处理
routes
POST /app/errorFile cn.mydomain.web.controllers.Controller.uploadErrorFile()
public Result uploadErrorFile(){
Http.MultipartFormData body = request().body().asMultipartFormData();
if(body == null){
return badRequest();
}
Http.MultipartFormData.FilePart errorFile = body.getFile("errorFile");
if (errorFile != null) {
String fileName = errorFile.getFilename();
logger.info("uploadErrorFile fileName=" + fileName);
File file = errorFile.getFile();
File targetDir = new File(GlobalData.ERROR_FILE_PATH);
if(!targetDir.exists()){
targetDir.mkdirs();
}
try {
IOUtils.copy(new FileInputStream(file), new FileOutputStream(GlobalData.ERROR_FILE_PATH + fileName));
} catch (IOException e) {
logger.error("upload error file fail filename=" + fileName, e);
}
return ok("File uploaded");
} else {
return badRequest();
}
}