Android 개발하면서 이것저것 죽어버리는 오류를 개발중에는 Logcat
을 보면서 바로 체크하면되지만
테스트 도중에 일어나면 … 속수무책입니다.
그래서, 개발시에는 Crash 관련 처리를 미리 해두는것이 좋습니다.
Crash 가 일어난 원인 체크 및 추가적으로 레드마인
, JIRA
, GitLab
등 이슈 트래킹에 도움이 됩니다.
Android 개발시 Java의 Crash 로그 수집의 기본처리는 아래와 같습니다.
public class MyUncaughtExceptionHandler
implements Thread.UncaughtExceptionHandler {
// <!-- 생략 -->
@Override
public void uncaughtException(final Thread thread, final Throwable ex) {
// <!-- 필요한 처리 -->
}
}
// reportHandler 는 Thread.UncaughtExceptionHandler 를 상속받은 클래스의 인스턴스
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
위의 소스처럼 기본적인 Thread의 에러처리 핸들러를 UncaughtExceptionHandler
상속받은 클래스의 인스턴스를 적용하는것으로 끝이다.
그 후, 필요한 처리를 uncaughtException()
함수내에서 하시면됩니다.
기본적으로 stacktrace
등의 정보를 수집하면 됩니다.
JNI 내부에서 에러가나는 경우에도 stacktrace
체크는 가능하지만, 실질적인 에러가 일어난 C
, C++
의 파일 위치 확인은 불가능합니다.
그래서 대안은 Google Breakpad
가 있습니다.
Google Breakpad는 Cross Platform을 지원하며 이미 여러 프로그램에서 사용중입니다. 예로는 Chrome Browser, Firefox, Camino, Google Picasa, Google Earth 등이 있습니다.
하지만, 현재 Google Code
는 새로운 프로젝트를 더이상 등록이 불가능하고, Google 측의 전체 프로젝트가 GitHub로 이관 예정이지만, 아직 Google Breakpad 는 이관되지 않았습니다.
추후 Google Code 접근 종료시 제 개인 GitHub Repository를 이용하시길 바랍니다.
해당순서는 Android Studio
기준으로 작성했습니다.
기본적인 View Injection 은 butterknife
를 사용했습니다.
System.loadLibrary("test_google_breakpad");
native void initNative(String path);
native void crashService();
initNative
메소드는 추후 DUMP 파일이 저장될 곳을 지정하기위해 path 를 매개변수로 받도록 정의했습니다.
기존 Eclipse 에서는 Command 창에서 ndk-build
를 입력해서 빌드 처리나 별도 작업했는데, Gradle 관련은 아래 Task 를 build.gradle 파일에 등록해줍니다.
task ndkBuild(type: Exec, description:'Compile JNI source via NDK') {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def command = properties.getProperty('ndk.dir')
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
command += "\\ndk-build.cmd"
} else {
command += "/ndk-build"
}
// Task내에서 Build Type을 검사할 수 없기 때문에 local.properties의 ndk.dir로
// Debug 가능한 Library를 Build할 지 검사한다.
def isDebug = true
if (isDebug) {
commandLine command, 'NDK_DEBUG=1', '-C', file('src/main').absolutePath
} else {
commandLine command, '-C', file('src/main').absolutePath
}
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn ndkBuild
}
#include <jni.h>
#include <stdio.h>
#include <android/log.h>
extern "C" {
void Java_com_pluusystem_breakpadjavacall_MainActivity_initNative(JNIEnv* env, jobject obj, jstring filepath)
{
__android_log_print(ANDROID_LOG_DEBUG, "PluuSystem", "initNative cal");
}
void Java_com_pluusystem_breakpadjavacall_MainActivity_crashService(JNIEnv* env, jobject obj)
{
__android_log_print(ANDROID_LOG_DEBUG, "PluuSystem", "crashService call");
}
}
JNI 호출시 해당 C, C++ 파일의 함수 정의 양식은 Java_JNI 호출 패키지_호출 패키지_호출 함수
과 같습니다.
올바른 설정이 되었다면 다음 로그가 출력됩니다.
initNative call
crashService call
Step1 소스 : Step1 링크
Google Breakpad 원본 - 현재 프로젝트 비교
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_DEFAULT_CPP_EXTENSION := cpp
LOCAL_MODULE := test_google_breakpad
LOCAL_SRC_FILES := test_breakpad.cpp
LOCAL_STATIC_LIBRARIES += breakpad_client
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)
# If NDK_MODULE_PATH is defined, import the module, otherwise do a direct
# includes. This allows us to build in all scenarios easily.
ifneq ($(NDK_MODULE_PATH),)
$(call import-module,google_breakpad)
else
include $(LOCAL_PATH)/google_breakpad/Android.mk
endif
Google Breakpad 원본 - 현재 프로젝트 비교
LOCAL PATH
LOCAL_SRC_FILES 추가
src/client/linux/dump_writer_common/thread_info.cc \
src/client/linux/dump_writer_common/seccomp_unwinder.cc \
src/client/linux/dump_writer_common/ucontext_reader.cc \
src/client/linux/microdump_writer/microdump_writer.cc \
DUMP 파일 수집을 위해 Google Breakpad 관련 내용을 적용합니다.
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include "google_breakpad/src/client/linux/handler/exception_handler.h"
#include "google_breakpad/src/client/linux/handler/minidump_descriptor.h"
static google_breakpad::ExceptionHandler* exceptionHandler;
bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
void* context,
bool succeeded) {
__android_log_print(ANDROID_LOG_DEBUG, "PluuSystem", "Dump path: %s\n", descriptor.path());
return succeeded;
}
void Crash() {
volatile int* a = reinterpret_cast<volatile int*>(NULL);
*a = 1;
}
extern "C" {
void Java_com_pluusystem_breakpadjavacall_MainActivity_initNative(JNIEnv* env, jobject obj, jstring filepath)
{
const char *path = env->GetStringUTFChars(filepath, 0);
google_breakpad::MinidumpDescriptor descriptor(path);
exceptionHandler = new google_breakpad::ExceptionHandler(descriptor, NULL, DumpCallback, NULL, true, -1);
__android_log_print(ANDROID_LOG_DEBUG, "PluuSystem", "initNative cal");
}
void Java_com_pluusystem_breakpadjavacall_MainActivity_crashService(JNIEnv* env, jobject obj)
{
__android_log_print(ANDROID_LOG_DEBUG, "PluuSystem", "crashService call");
Crash();
}
}
앱 실행 후 NATIVE CRASH 버튼을 선택하면 다음 로그가 출력됩니다.
Dump path: /storage/emulated/0/Android/data/com.pluusystem.breakpadjavacall/cache/741afdff-1cb4-feb8-6890f070-16e328fa.dmp
741afdff-1cb4-feb8-6890f070-16e328fa.dmp 대신 다른 파일이 출력됩니다.
Step2 소스 : Step2 링크
public class NativeController {
public static int NativeCrashCallback(String fileName) {
Log.d("PluuSystem", "NativeCrashCallback Called");
Log.d("PluuSystem", "Dump FileName=" + fileName);
return 0;
}
}
bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
void* context,
bool succeeded) {
__android_log_print(ANDROID_LOG_DEBUG, "PluuSystem", "Dump path: %s\n", descriptor.path());
jclass cls = jEnv->FindClass("com/pluusystem/breakpadjavacall/NativeController");
if(cls == NULL) {
return false;
}
jmethodID mhthod = jEnv->GetStaticMethodID(cls, "NativeCrashCallback", "(Ljava/lang/String;)I");
if(mhthod == NULL) {
return false;
}
const char* path = descriptor.path();
jstring jstr = jEnv->NewStringUTF(path);
if (jstr == NULL) {
return false;
}
jint i = jEnv->CallStaticIntMethod(cls, mhthod, jstr);
return succeeded;
}
최초 Java_com_pluusystem_breakpadjavacall_MainActivity_initNative
호출시 전달받은 JNIEnv* env
객체를 이용해서 다음 로직을 호출하여 자바의 함수를 호출합니다.
JNI 작업 순서
해당 작업 완료 후, Crash 가 일어났을때 나타나는 로그
NativeCrashCallback Called
Dump FileName=/storage/emulated/0/Android/data/com.pluusystem.breakpadjavacall/cache/649a81f1-b6be-90fa-6532828a-0ce9ba0c.dmp
Step3 소스 : Step3 링크
###추천 Android 관련 Crash Report Service
추천하는 서비스는 아래를 참고하시길 바랍니다.
비고 | Fabric | Acra | UrQA | HockeyApp |
---|---|---|---|---|
Java | O | O | O | O |
JNI | X | X | O | O |
JNI Module | - | - | Google Breakpad | Google Breakpad |
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024