تالار گفتمان nCIS.ir

نسخه‌ی کامل: جلوگیری از Crash برنامه درصورت بروز خطا
شما در حال مشاهده نسخه آرشیو هستید. برای مشاهده نسخه کامل کلیک کنید.
خیلی وقت‌ها پیش میاد که برنامه بخاطر خطایی که قبلاً پیش‌بینی نکردیم (و درنتیجه توی بلاک try/catch هم نگذاشتیم) با یه خطای آزاردهنده بسته میشه. ازطرفی اگه این خطا جزو استثناهای چک‌نشده باشه (Unchecked Exceptions)، کامپایلر هم به ما گیر نمیده که مدیریتش کنیم. درنتیجه تجربه‌ی خوبی برای کاربر ایجاد نمیشه و ازطرفی ما هم ممکنه نتونیم اون شرایط رو در عمل اجرا کنیم تا ببینیم خطایی که به ما گزارش‌کردن چی بوده. مثلاً ممکنه کاربر از یه دستگاه خاصی استفاده کنه که ما برای تست برنامه در اختیار نداشته باشیم و امکان خریداری و استفاده از اون رو هم نداشته باشیم.

اینجور وقتها تنها راهی که بشه فهمید خطا چی بوده و جلوی بسته‌شدن ناجور برنامه رو هم بگیریم، کنترل خطا بصورت دستی و تغییر مکانیزم پیشفرض جاوا برای کنترل استثناهای مدیریت‌نشده (Uncaught Exceptions) هست. توی این تاپیک قصد دارم مرحله به مرحله روش انجام این کار رو توضیح بدم.
از اونجا که هر کاری رو عادت دارم با کمک کلاس اختصاصی Application انجام بدم، اینجا هم ابتدا یه کلاس App اضافه میکنیم:
public class App extends Application {
    public static Context context;
    public static SharedPreferences preferences;
    public static SharedPreferences.Editor editor;

    @Override
    public void onCreate() {
        context = getApplicationContext();
        preferences = PreferenceManager.getDefaultSharedPreferences(context);
        editor = preferences.edit();
        edit.commit();
    }
}

این کلاس خلاصه‌ترین حالت کلاس App هست که توی این مورد خاص بهش نیاز داریم. حالا کافیه توی مانیفست معرفیش کنیم:
<application
    android:name=".App"
    ...>
خوب حالا باید یه کلاس برای مدیریت خطاها بنویسیم:
public class CustomExceptionHandler implements Thread.UncaughtExceptionHandler {
   private final Activity activity;
   private final Class<?> c;

   public CustomExceptionHandler(Activity activity, Class<?> c) {
       this.activity= activity;
       this.c = c;
   }

   @Override
   public void uncaughtException(Thread t, Throwable e) {
       StringWriter stackTrace = new StringWriter();
       e.printStackTrace(new PrintWriter(stackTrace));
       App.editor
               .putBoolean("error", true)
               .putString("error_string", stackTrace.toString())
               .putString("error_time", String.valueOf(System.currentTimeMillis()))
               .apply();
       Intent intent = new Intent(activity, c);
       intent.putExtra("error", true);
       activity.startActivity(intent);
       activity.finish();
       Process.killProcess(Process.myPid());
       System.exit(0);
   }
}

همونطور که میبینید، سازنده‌ی این کلاس فقط دو پارامتر میگیره که یکی Activity و یکی دیگه هم Class هست و به‌ترتیب مشخص‌کننده‌ی اکتیویتی تولید خطا و کلاس مربوط به اکتیویتی مقصد (اکتیویتی شروع یا Launcher برنامه) هستن و فیلدهای متناظر باهاشون رو مقداردهی میکنه.

متد اصلی این کلاس، uncaughtException هست که Override کردیم. توی این متد داریم ابتدا StrackTrace (پشته‌ی لاگ خطا) رو توی یه رشته به اسم stackTrace ذخیره میکنیم و بعد این خطا رو همراه با کلید error و زمان بروز خطا توی SharedPreferences ذخیره میکنیم.

بعد یه Intent میسازیم که ما رو از زمینه‌ی بروز خطا (اکتیویتی که خطا تولیدشده) به کلاس مقصد (که میتونه هر اکتیویتی موردنظر برنامه باشه) منتقل کنه. بعد هم اینتنت رو اجرا میکنیم و Process جاری برنامه رو میبندیم و از برنامه خارج میشیم. البته هنوز از این کلاس استفاده نکردیم.

برای استفاده از این کلاس باید توی تمام اکتیویتی‌های خودمون این کد رو توی onCreate و بعد از فراخوانی super.onCreate(); و قبل از تمام دستورات دیگه بنویسیم:
Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(this, MainActivity.class));

و بجای MainActivity اسم اکتیویتی آغاز به کار برنامه‌ی خودمون رو قرار بدیم.

حالا اگه هر خطایی رخ بده که ما مدیریت نکرده باشیم، اون خطا توی SharedPreferences ذخیره میشه و بعد برنامه Restart میشه.
حالا وقتی که توی اکتیویتی اصلی برنامه هستیم، چک میکنیم که برنامه دفعه‌ی قبل با خطا بسته شده یا نه. برای این‌کار هم میتونیم چک کنیم توی SharedPreferences کلید خطا وجود داشته باشه و هم از Extra خاصی که موقع بروز خطا به Intent اضافه کرده بودیم کمک بگیریم. من اینجا با روش دوم کار کردم:
Bundle bundle = getIntent().getExtras();
if (bundle != null && bundle.containsKey("error")) {
   String error = App.preferences.getString("error_string", ""); // Error Stack Trace
   String ts = App.preferences.getString("error_time"); // Error TimeStamp
   String manufacturer = Build.MANUFACTURER; // Device Factory
   String model = Build.MODEL; // Device Model
   String api = String.valueOf(Build.VERSION.SDK_INT); // Android API Version

   // Send error info to server

   bundle.clear();
   App.editor
           .remove("error")
           .remove("error_string")
           .remove("error_time")
           .apply();
   Toast.makeText(App.context, "Sorry for the problem occured. Error log was sent to the server.");
}

جایی که نوشتم Send error info to server میتونین با هر روشی که صلاح میدونین و توی پروژه ازش استفاده کردین، خطاها رو برای سرور خودتون ارسال کنین و بعداً سر فرصت بخونین. حتی میتونین موقع ذخیره‌کردن خطا، شماره‌ی رکورد خطا رو برگردونین و توی برنامه به کاربر نشون بدین تا اگه به مشکل برخورد، وقتی با پشتیبانی تماس میگیره بگه که کد خطای من فلان بوده و بتونین دقیق‌تر متوجه بشین که مشکل چی بوده و رفعش کنین.

امیدوارم که این آموزش براتون مفید واقع شده باشه.