Service ها در اندروید #۲

در پست قبل اشاره شد که سه نوع سرویس در اندروید وجود دارد.یکی از آن ها Startedها است.برای ساخت این نوع سرویس ها می توانیم از دو کلاس ارث بری کنیم:

  1. کلاس Service:کلاس پایه برای ساخت یک سرویس است و مهم است که در کلاس های مشتق شده یک thread مجزا ایجاد شود تا عملکرد کلی برنامه تحت تاثیر قرار نگیرد.
  2. کلاس IntentService:زمانیکه از این کلاس ارث بری می کنید نیازی نیست که یک thread مجزا ساخته شود.البته این نوع سرویس ها امکان مدیریت کردن چند درخواست همزمان را ندارند.

مثالی از پیاده سازی یک IntentService :

public class HelloIntentService extends IntentService {

  /**
   * A constructor is required, and must call the super IntentService(String)
   * constructor with a name for the worker thread.
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * The IntentService calls this method from the default worker thread with
   * the intent that started the service. When this method returns, IntentService
   * stops the service, as appropriate.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      try {
          Thread.sleep(5000);
      } catch (InterruptedException e) {
          // Restore interrupt status.
          Thread.currentThread().interrupt();
      }
  }
}

توجه به این نکته ضروری است که در صورت پیاده سازی هر کدام از callbackهای onCreate(), onStartCommand(), یا onDestroy() باید کلاس super  فراخوانی شود مگر در  callback مربوط به onBind.

نمونه ای از پیاده سازی کلاس پایه Service

public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              // Restore interrupt status.
              Thread.currentThread().interrupt();
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service.  Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block.  We also make it
    // background priority so CPU-intensive work will not disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

onStartCommand() نیاز دارد که یک int برگردانده شود.این مقدار مشخص می کند که در صورت kill شدن سرویس توسط android چگونه باید ادامه کارش را انجام دهد.در این متد می توان از constهای زیر برای بازگشت استفاده کرد:

  • START_NOT_STICKY:در این حالت اگر سرویس kill شود دیگر ساخته نمی شود مگر اینکه pending intentی برای تحویل وجود داشته باشد.
  • START_STICKY:اگر سیستم اندروید سرویس را kill  کند در این حالت دوباره ساخته می شود و متد onStartCommand() دوباره فراخوانی خواهد شد ولی آخرین intent دریافتی را دوباره تحویل نخواهد داد.همچنین pending intentها را نیز دوباره تحویل خواهند داد.
  • START_REDELIVER_INTENT:مثل START_STICKY است با این تفاوت که آخرین intent را دوباره تحویل می دهد.این برای زمانی مثل دانلود فایل مناسب است.

توجه به این نکته ضروری است که باید بعد از اتمام کار سرویس حتما سرویس مورد نظر stop شود.

Service ها در اندروید

سرویس ها به منظور انجام کارهای background در اندروید بوجود آمده اند.این کارها ممکن است برای مدت زمان طولانی در جریان باشند.اجزای تشکیل دهنده یک برنامه اندرویدی می توانند یک سرویس را  اجرا کند.یک سرویس می تواند در background در حال اجرا باشد حتی اگر کاربر برنامه جاری را ترک کند و به برنامه دیگری برود.

در اندروید سه نوع سرویس داریم:

Scheduled:

از اندروید ۵ به بعد مفهومی به اسم سرویس های زمانبندی شده بوجود آمده که توسط آن می توانیم یک کار را برای اجرا در background زمانبندی کنیم.

Started:

اجزای برنامه  می توانند یک سرویس را به وسیله startService() اجرا کنند.این سرویس می تواند به صورت ادامه دار درحال اجرا باشد حتی اگر خود اجرا کننده سرویس از بین برود.معمولا این دسته از سرویس ها خروجی خاصی ندارند و برای انجام یک کار مشخص مثل دانلود یک فایل مورد استفاده قرار می گیرند.

Bound:

اجزای برنامه(مثل یک activity) می توانند به یک سرویس متصل شوند و مانند کلاینت سرور به سرویس به عنوان سرویس دهنده یک درخواست ارسال کنند و جواب آن را دریافت کنند.به طور همزمان چند کلاینت می توانند به یک سرویس متصل شوند و از آن سرویس بگیرند ولی به محض اینکه یکی از آن ها اتصالش از سرویس قطع شد سرویس مورد نظر ما از بین خواهد رفت.

البته که یک سرویس می تواند هم به صورت Started و هم به صورت Bound مورد استفاده قرار بگیرد.همچنین اگر بخواهید یک سرویس را از دید باقی برنامه ها در اندروید مخفی کنید باید از آن را در فایل مانیفست private قرار دهید.

توجه به این نکته ضروری است که سرویس ها در thread اصلی برنامه اجرا می شوند.به عبارتی اگر عملیاتی که در سرویس انجام می دهید نیازمند پردازش سی پی یو هست و یا منجر به لاک می شود بهتر است برای آن یک thread جدید ساخته شود.

برای ساخت یک سرویس نیاز است که از کلاس Service  ارث بری کنید.همچنین callbackهای مورد نیاز را نیز پیاده سازی کنید.در زیر چند callback مورد نیاز نام برده شده است:

onStartCommand():

زمانیکه یک سرویس بنابه درخواست یک جز دیگر شروع به کار می کند این متد فراخوانی  می شود.توجه به این موضوع لازم  که باید سرویس را به وسیله stopSelf() یا  stopService() متوقف کنیم.

onBind():

زمانیکه یک جز برنامه توسط bindService() به سرویس متصل می شود این متد فراخوانی می شود.اگر نمی خواستیم اجزای دیگر به سرویس متصل شوند مقدار null را بازگشت دهیم.

onCreate():

اولین باری که سرویس ساخته می شود این متد فراخوانی می شوند.

onDestroy():

زمانی که یک سرویس از بین می رود این متد صدا زده می شود.فقط باید توجه داشت که باید دراین متد کارهایی از قبیل پاکسازی threadها و … انجام شود.

اندروید سرویسی که در حال کار باشد و یا کاربر به آن وابسته است را با اولویت کمتری kill  می کند.در هر صورت بعد از kill کردن امکان دارد زمانی که مموری کافی در اختیار سیستم عامل قرار گرفت سرویس دوباره شروع به کار کند.البته این موضنوع قابل تنظیم است ک در بخش های بعدی بیشتر توضیح داده خواهد شد.

Fragment در اندروید

Fragment ها اجزای قابل استفاده مجدد در یک activity هستند.

فرض کنید در حال ساخت یک برنامه ایمیل هستید.اگر برنامه شما روی گوشی در حال اجرا باشد دارای یک صفحه برای نمایش لیست ایمیل ها و یک صفحه برای جزئیات ایمیل ها خواهد بود.حال فرض کنید بخواهید که در تبلت ها یک صفحه ۲ ستونه برای این کار داشته باشید.رویکرد بدون استفاده از Fragment این هست که طراحی به صورت کامل برای گوشی و تبلت مجزا باشد .ولی با استفاه از Fragmentها نیاز به طراحی مجزا نیستیم.

fragments

lifecycle یک Fragment  به صورت مشخص تاثیر گرفته از activityی است که در آن قرار گرفته است.مثلا زمانی که یک activity به وضعیت paused می رود تمام fragmentهای داخل آن نیز به وضیعت paused می روند و همچنین اگر یک activity  به وضعیت destroyed برود ، fragmentها نیز destroyed می شوند.

می توانیم fragmentها به activity اضافه و یا حذف کنیم.همچنین می توانیم آن ها را در back stack قرار دهیم.برای ایجاد یک Fragment نیاز هست که از کلاس Fragment ارث بری کنید.

fragmentها نیز به مانند activity ها دارای callbackهای onCreate(), onStart(), onPause(), و onStop() هستند که در صورت نیاز باید از آن ها استفاده کنیم.

lifecycle یک Fragment در تصویر زیر قابل مشاهده است:

fragment_lifecycle

onCreate():

زمانی که سیستم در حال ساخت Fragment است این متد فراخوانی می شود و در آن می توانیم مواردی را مقدار دهی کنیم که نیاز است در هر بار paused یا stopped از بین نروند.

onCreateView():

زمانی که سیستم در حال ترسیم ui هست این متد فراخوانی می شود که ما باید به عنوان خروجی یک view بر گردانیم.همچنین در صورتی که Fragment ما ui ندارد می بایست null برگشت دهیم.

onPause():

زمانی که کاربر Fragment را ترک می کند این متد فراخوانی می شود.بهتر است اطلاعاتی که کاربر نیاز دارد را ذخیره کنیم .

onAttach():

زمانی که Fragment  برای اولین بار با activity وصل شد.

onDetach():

زمانی که Fragment از activity جدا شد.

برای اضافه کردن ui به fragment به شکل زیر عمل می کنیم:

public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}

از طریق دو راه می توان fragment را به یک activity اضافه کرد:

تعریف fragment داخل layout.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:name="com.example.news.ArticleListFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    <fragment android:name="com.example.news.ArticleReaderFragment"
            android:id="@+id/viewer"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>

استفاده از fragmentManager.

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();

همچنین اگر نیاز بود که Fragment را به backstack اضافه کنید باید به شکل زیر عمل کنید:


// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

برای بازیابی Fragment ها باید متد onBackPressed به شکل زیر پیاده شود:

@Override
public void onBackPressed() {
    if (getFragmentManager().getBackStackEntryCount() &amp;gt; 0) {
        getFragmentManager().popBackStack();
    } else {
        super.onBackPressed();
    }
}

دقت کنید اگر در یک transaction کارهای مختلفی از قبیل اضافه کردن،حذف کردن و جایگزین کردن را انجام داده باشید با زدن دکمه back همه آنها یک جا برگردانده می شوند.
توجه به این نکته ضروری است که صدا زدن commit() به معنی انجام شدن تراکنش در همان لحظه نیست.بلکه تراکنش صف خواهد شد و در زمانی که نوبتش رسید اجرا می شود.

تصویر زیر نشان دهنده lifecycle یک activity در مقابل lifecycle یک fragment است:

activity_fragment_lifecycle

 

Lifecycle یک برنامه در اندروید

در اندروید برنامه ها در پروسس لینوکسی مجزا و مخصوص به خود اجرا می شوند.نکته مهم این هست که Lifecycle یک برنامه در اندروید توسط برنامه نویس به صورت مستقیم مدیریت نمی شود بلکه خود سیستم اندروید آن را مدیریت می کند.ولی استفاده از component iهای مختلف در اندروید روی این چرخه اثر می گذارند.

سیستم عامل اندروید زمانی که نیاز به منابع بیشتر مثل RAM باشد یک برنامه را kill می کند.البته این کار را به وسیله مکانیزیمی انجام می دهد که در زیر توضیح داده شده است:

  • زمانیکه یک برنامه در حال اجرا باشد(foreground process) آن برنامه را تا جای ممکن kill نمی کند.سیستم اندروید برنامه را در حالات زیر در حال اجرا فرض می کند:
    • یک Activity (+ +) روی صفحه در حال اجرا باشد(وضعیت آن Resumed باشد)
    • یک BroadcastReceiver در حال اجرا باشد.(متد onReceive() آن صدا زده شده باشد)
    • یک Service در حال اجرا باشد.(در حال اجرای یکی از متد های Service.onCreate(), Service.onStart(), یا Service.onDestroy())
  • زمانیکه  یک برنامه قابل رویت(visible process) داشته باشیم.در این حالت سیستم عامل اندروید تا جای ممکن برنامه را kill نمی کند.در حالات زیر برنامه قابل رویت فرض می شود
    • یک Activity روی صفحه در وضعیت paused قرار دارد.مثلا یک دیالوگ روی صفحه باشد.
    • یک سرویس به صورت foreground service اجرا شده باشد.
    • سرویس هایی مثل live wallpaper در حال استفاده باشند.
  • service process که به وسیله متد startService() اجرا می شوند. این دسته از سرویس ها مستتقیم قابل رویت نیستند ولی کارهایی از قبلی دانلود و … انجام می دهد که ممکن است برای کاربر مهم باشد.از این رو سیستم اندروید سعی می کند تا جای ممکن این پروسس ها را از بین نبرد.توجه داشته باشید که سرویسهایی که زمان اجرای طولانی داشته باشند مثلا بیشتر از ۳۰ دقیقه به منظوری جلوگیری از memory leak آن ها را به cached processes انتقال می دهد.
  • cached process پروسس هایی هستند که هم اکنون در حال استفاده و اجرا نمی باشند.سیستم اندروید در حالت عادی ممکن است دارای چندین cached process باشد و در زمانی که نیاز به رم داشت از قدیمی ترین پروسس شروع به kill کردن می کند.زمانهای کمی پیش می آید که نیاز باشد تمام پروسس های کش شده را kill کند.همچنین در صورتیکه یک پروسس را kill کند Lifecycle مهم یک activity را اجرا می کند.این کار باعث می شود در صورتیکه یکبار دیگر به آن برنامه نیاز شد برنامه در یک پروسس جدید با همان وضعیت قبلی اجرا شود.

Parcelable و Bundle

دو شی Parcelable و Bundle به منظور پاس دادن داده بین activityها ،برنامه های مختلف و همچنین نگهداری وضعیت در تغییر configurationها (تغییر زبان و یا تغییر جهت تلفن)کاربرد دارند.

برای انتقال داده بین activityهای مختلف می توان از شی Bundle استفاده کرد:

Intent intent = new Intent(this, MyActivity.class);
intent.putExtra("media_id", "a1b2c3");
...
startActivity(intent);

مستندات اندروید نیز پیشنهاد می کنید برای تایپ های ساده مثل int و string از شی Bundle استفاده شود.ولی در صورتیکه نیاز دارید یکه شی پیچیده را بین  activityها جابجا کنید در این صورت نیاز به Parcelable  خواهید داشت.

public class Student implements Parcelable{
        private String id;
        private String name;
        private String grade;

        // Constructor
        public Student(String id, String name, String grade){
            this.id = id;
            this.name = name;
            this.grade = grade;
       }
       // Getter and setter methods
       .........
       .........

       // Parcelling part
       public Student(Parcel in){
           String[] data = new String[3];

           in.readStringArray(data);
           // the order needs to be the same as in writeToParcel() method
           this.id = data[0];
           this.name = data[1];
           this.grade = data[2];
       }

       @Оverride
       public int describeContents(){
           return 0;
       }

       @Override
       public void writeToParcel(Parcel dest, int flags) {
           dest.writeStringArray(new String[] {this.id,
                                               this.name,
                                               this.grade});
       }
       public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
           public Student createFromParcel(Parcel in) {
               return new Student(in); 
           }

           public Student[] newArray(int size) {
               return new Student[size];
           }
       };
   }

همانطور که در بالا مشاهده می کنید برای ایجاد یک Parcelable نیاز است که Parcelable را پیاده سازی کنید.همچنین برای استفاده از آن به شکل زیر باید عمل کنید :

intent.putExtra("student", new Student("1","Mike","6"));

گوگل در مستندات اندروید پیشنهاد می کند که در صورتیکه نیاز است بین دو پروسس مجزا داده ای را ارسال کنید از Parcelable سفارشی استفاده نکنید زیرا در این حالت نیاز است که در دو برنامه یک ورژن مشخص از شی مورد نظر نگهدارید.برای مثال در بالا Student در برنامه مبدا و مقصد باید یکی باشد.

Tasks and Back Stack

هر برنامه از چندین activity تشکیل شده است.هر activity  را می توان توسط خود برنامه و یا سایر برنامه ها فراخوانی کرد.برای مثال برای ارسال ایمیل می توان به وسیله intent درخواست باز شدن یک activity  را به سایر برنامه ها ارسال کرد.

در سیستم اندروید مفهومی به اسم Task وجود دارد که در آن تعدادی activity وجود دارد.این activityها در یک stack نگهداری می شوند.زمانیکه کاربر یک برنامه را باز می کند ،سیستم اندروید در صورتیکه Task مربوطه وجود داشته باشد آن را دوباره اجرا می کند و در صورتیکه Taskی برای برنامه وجود نداشته باشد ،یک Task جدید خواهد ساخت.

توجه به این نکته ضروری است که به هیچ عنوان activity ها در stack خارج از push و pop  کردن arrange مجدد نمی شوند.

در عکس پایین شما فرایند افزودن و پاک شدن یک activity را در stack مشاهده می کنید:

diagram_backstack.png

زمانی که کاربر با زدن دکمه back تمام activity را از stack پاک کند و به صفحه خانه(Home screen)برگردد Task از بین خواهد رفت.

فرض کنید می خواهید دو برنامه a و b را اجرا می کنید.در این حالت با اجرا کردن برنامه a یک تسک برای آن ساخته می شود و در صورتیکه با استفاده از دکمه home  به صفحه خانه(Home screen) برگردید تسک a  در background قرار خواهد گرفت و تمام activityهای آن به وضعیت stopped تغییر وضعیت خواهند داد. حال اگر برنامه b را اجرا کنید یک تسک جدید ایجاد خواهد شد و این تسک در foreground قرار خواهد گرفت.

سیستم اندروید توانایی نگهداری تعداد زیادی task را در background دارد ولی توجه به این نکته لازم است که در صورتیکه سیستم اندروید نیاز داشته باشد، برای بازیابی فضای ram تعدادی از آن ها را ز بین ببرد.

نکته :امکان این وجود دارد که از یک activity  چندین instance در stack وجود داشته باشد.

diagram_multitasking

 

نکاتی در رابطه با lifecycle یک Activity

زمانی که به هر عنوان صفحه  از حالت عمودی به افقی و یا برعکس تغییر جهت دهد در این حالت وضعیت activity به وضعیت destroyed  تغییر خواهد کرد و دوباره ساخته خواهد شد.نکته قابل توجه این است که شما باید متد onSaveInstanceState() را پیاده سازی کنید و اطلاعاتی که نیاز دارید (به غیر از اطلاعاتی خود سیستم اندروید به طور اتوماتیک ذخیره و بازبابی می کند) را در شی bundle ذخیره کنید.

زمانی که کاربر دکمه back دستگاه را فشار می دهد در این حالت activity به destroyed رفته و instance آن از بین خواهد رفت.توجه داشته باشید که دیگر onSaveInstanceState فراخوانی نخواهد شد.زیرا در این حالت سیستم فرض می کند که شما دیگر قصد بازگشت به activity را ندارید.به هر حال با استفاده از onBackPressed() می توانید قبل از اینکه activity از بین برود از کاربر کانفرم دریافت کنید.

 

lifecycle یک Activity بخش دوم

در پست های قبلی در رابطه با lifecycle یک Activity در اندروید به صورت مختصر توضیح داده شد.در این پست سعی بر این است که به صورت پایه ای تر به lifecycle بپردازیم.

onCreate()

این متد(callback) زمانی که سیستم activity را می سازد صدا زده می شود.در زمان اجرای این متد activity  در وضعیت Created قرار می گیرد.در این متد منطق اولیه برای activity  پیاده می شود.برای مثال اجرای یک thread ، bind کردن یک list و مقدار دهی متغیر های سطح activity در متد onCreate انجام می شود.این متد دارای یک پارامتر ورودی به اسم savedInstanceState از جنس Bundle  است که اگر activity  برای اولین بار است که اجرا می شود برابر است با null.هدف از savedInstanceState نگهداری وضعیت قبلی activity است.

TextView mTextView;

// some transient state for the activity instance
String mGameState;

@Override
public void onCreate(Bundle savedInstanceState) {
    // call the super class onCreate to complete the creation of activity like
    // the view hierarchy
    super.onCreate(savedInstanceState);

    // recovering the instance state
    if (savedInstanceState != null) {
        mGameState = savedInstanceState.getString(GAME_STATE_KEY);
    }

    // set the user interface layout for this Activity
    // the layout file is defined in the project res/layout/main_activity.xml file
    setContentView(R.layout.main_activity);

    // initialize member TextView so we can manipulate it later
    mTextView = (TextView) findViewById(R.id.text_view);
}

// This callback is called only when there is a saved instance previously saved using
// onSaveInstanceState(). We restore some state in onCreate() while we can optionally restore
// other state here, possibly usable after onStart() has completed.
// The savedInstanceState Bundle is same as the one used in onCreate().
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    mTextView.setText(savedInstanceState.getString(TEXT_VIEW_KEY));
}

// invoked when the activity may be temporarily destroyed, save the instance state here
@Override
public void onSaveInstanceState(Bundle outState) {
    out.putString(GAME_STATE_KEY, mGameState);
    out.putString(TEXT_VIEW_KEY, mTextView.getText());

    // call superclass to save any view hierarchy
    super.onSaveInstanceState(out);

کد بالا نشان دهنده نحوه استفاده از متد onCreate است.

زمانی که activity از وضعیت created خارج شد به وضعیت Started تغییر وضعیت خواهد داد.

onStart()

این متد آماده کننده activity برای نمایش و همچنین برای تعامل با کاربر است.متد onStart() همانند متد onCreate به سرعت خاتمه می یابد.به عبارتی وضعیت activity در حالت Started نخواهد ماند.

onResume()

بعد از اینکه activity آماده تعامل با کاربر شد صدا زده می شود و در این حالت activity  به وضعیت Resumed تغییر وضعیت می دهد.این وضعیت تا زمانی که برای activity حالتی رخ ندهد که focus را از دست بدهد ادامه خواهد یافت.حالت هایی که ممکن است activity  فوکس را از دست بدهد:

  • خاموش شدن صفحه
  • رفتن به یک activity دیگر
  • تماس ورودی
  • و …

در حالت های بالا  activityبه وضعیت Paused تغییر وضعیت خواهد داد و سیستم اندروید متد onPause() را صدا می زند.اگر به هر عنوان دوباره activity از وضعیت Paused به وضعیت Resumed تغییر وضعیت دهد متد onResume فراخوانی خواهد شد.بنابراین یک activity به طور مرتب بین وضعیت Paused و Resumed تغییر وضعیت خواهد داد.

همچنین ممکن است نیاز باشد که در متد onResume منابعی که در متد onPause رها کرده بودید دوباره مقدار دهی کنید:

@Override
public void onResume() {
    super.onResume();  // Always call the superclass method first


    // Get the Camera instance as the activity achieves full user focus
    if (mCamera == null) {
        initializeCamera(); // Local method to handle camera init
    }
}

onPause()

این متد زمانی فراخوانی می شود که کاربر به هر عنوان activty را ترک کند:

  • مواردی که در onResume گفته شد.
  • در اندروید ۷٫۰ (API level 24) ممکن است هنگام استفاده از قابلیت multi-window mode برنامه جاری ما روی صفحه باشد ولی کاربر با برنامه دیگری در حال کار کردن است.به عبارتی focus روی برنامه ما نیست.
  • activty فعال است ولی focus به وسیله یک activty دیگر مثل دیالوگ گرفته شده است.

در متد onPause می توان منابعی که روی باتری تاثیر گذار هستند ولی کاربر به آنها نیاز ندارند را آزاد کرد.مثل  broadcast receiverها و یا سنسور ها.

برای مثال اگر activty در حال استفاده از دوربین هست onPause محل خوبی برای آزاد کردن است:

@Override
public void onPause() {
    super.onPause();  // Always call the superclass method first


    // Release the Camera because we don't need it when paused
    // and other activities might need to use it.
    if (mCamera != null) {
        mCamera.release();
        mCamera = null;
    }
}

مدت زمان اجرای onPause() بسیار کوتاه است و نباید عملیات های زمان بری مثل تراکنش دیتابیس و یا شبکه ای در این متد انجام شود.برای این منظور بهتر است از متد onStop()  که در ادامه توضیح داده خواهد شد استفاده کنیم.توجه داشته باشید که اگر یک activity به وضعیت Paused تغییر وضعیت دهد تا زمانی که به طور کامل Stoped و یا Resumed شود در وضعیت Paused خواهد ماند.

onStop()

زمانی که activity از دید خارج می شود(invisible) این متد صدا زده می شود.خیلی مهم هست که در متد onStop() منابع را آزاد کنیم.زیرا ممکن هست هیچ زمان onDestroy صدا زده نشود .همچنین اطلاعات مورد نیاز از activity جاری را نیز باید ذخیره کنیم:

@Override
protected void onStop() {
    // call the superclass method first
    super.onStop();

    // save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    // do this update in background on an AsyncQueryHandler or equivalent
    mAsyncQueryHandler.startUpdate (
            mToken,  // int token to correlate calls
            null,    // cookie, not used here
            mUri,    // The URI for the note to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No SELECT criteria are used.
            null     // No WHERE columns are used.
    );
}

توجه داشته باشید که onStop به معنی پایان یک activity نیست و سیستم اندروید کماکان activity را در رم نگه می دارد.همچنین اطلاعاتی که در view وجود دارد را نیز در صورت فعال شدن مجدد activity حفظ خواهد کرد.برای مثال اگر کاربر در حال تایپ یک مقدار در EditText باشد در صورتی که متد onStop صدا زده شود و دوباره activity فعال شود مقدار EditText حفظ خواهد شد.

حتی زمانی که سیستم اندروید activity را destroys می کند در این حالت باز وضعیت viewهای موجود در activity حفظ خواهند شد و به صورت اتوماتیک در شی savedInstanceState ذخیره و بازیابی خواهند شد.

onDestroy()

این متد زمانی که تابع finish() و یا سیستم تصمیم می گیرد پروسس را صدا بزند فراخوانی می شود.onDestroy() تمام منابعی که در onStop خالی نشده بودند را صدا می زند.

توجه داشته باشید که سیستم اندروید یک activity از kill نمی کند بلکه تمام پروسس و هر چه درون پروسس هست را در صورت نیاز kill خواهد کرد.همچنین شانس اینکه با یک برنامه در حال کار کردن باشید و سیستم تصمیم بگیرد آن پروسس را kill کند بسیار کم است.

معرفی ACRA برای مدیریت بهتر کرش کردن برنامه ها

نرم افزار های موبایل از آنجا که روی دستگاه های مختلف با برندهای متفاوتی اجرا می شوند ممکن است رفتار متفاوتی را از خود نشان دهند.به عنوان مثال ممکن است روی یک برند خاص به هر علت کرش کنند و بسته شوند.یکی از نیازهای مهم در توسعه نرم افزار های اندرویدی مدیریت بسته شدن های ناگهانی برنامه یا همان کرش کردن  است.

ACRA یکی از library هایی است که کار مدیریت کرش های برنامه را انجام می دهد و آن ها را به سمت یک سرور ارسال می کند.

راه اندازی ACRA ساده است و البته شما می توانید به راحتی پارامترهای آن را تغییر دهید.برای مثال کد زیر نشان دهنده نحوه ارسال کرش ها به یک سرور خاص است:

@ReportsCrashes(
        formUri = Const.Api + Const.ExceptionReportPath,
        reportType = HttpSender.Type.FORM,
        httpMethod = HttpSender.Method.POST,
        customReportContent = {
                ReportField.APP_VERSION_CODE,
                ReportField.APP_VERSION_NAME,
                ReportField.ANDROID_VERSION,
                ReportField.PACKAGE_NAME,
                ReportField.REPORT_ID,
                ReportField.BUILD,
                ReportField.STACK_TRACE,
                ReportField.BRAND,
                ReportField.PHONE_MODEL,
                ReportField.USER_CRASH_DATE,
                ReportField.USER_IP,
                ReportField.DEVICE_ID,
                ReportField.DUMPSYS_MEMINFO,
        },
        mode = ReportingInteractionMode.SILENT)

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        ACRA.init(this);
    }
}

همانطور که در کد بالا مشاهده می کنید پارامتر های زیر به سرور از طریق متد POST ارسال می شوند:

ReportField.APP_VERSION_CODE,
ReportField.APP_VERSION_NAME,
ReportField.ANDROID_VERSION,
ReportField.PACKAGE_NAME,
ReportField.REPORT_ID,
ReportField.BUILD,
ReportField.STACK_TRACE,
ReportField.BRAND,
ReportField.PHONE_MODEL,
ReportField.USER_CRASH_DATE,
ReportField.USER_IP,
ReportField.DEVICE_ID,
ReportField.DUMPSYS_MEMINFO,

اگر فرض کنیم سمت سرور یک برنامه asp.net mvc وظیفه ثبت این خطاها را بر عهده دارد به شکل زیر باید عمل کنیم:

    [Route("api/[controller]")]
    public class ExceptionReportController : BaseApiController
    {
        public ExceptionReportController(ApplicationDbContext ctx, IOptions<BaseSettings> settings) : base(ctx, settings)
        {
        }

        [HttpPost]
        public async Task Post([FromForm] ExceptionReport exceptionReport)
        {
            DbContext.ExceptionReport.Add(exceptionReport);
            await DbContext.SaveChangesAsync();
        }
    }

همچنین مدلی که به عنوان ورودی اکشن در نظر گرفته ایم به شکل زیر خواهد بود:

    public class ExceptionReport
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [Key]
        public int Id { get; set; }
        public string REPORT_ID { get; set; }
        public string ANDROID_VERSION { get; set; }
        public int APP_VERSION_CODE { get; set; }
        public double APP_VERSION_NAME { get; set; }
        public int AVAILABLE_MEM_SIZE { get; set; }
        public string BRAND { get; set; }
        public string DUMPSYS_MEMINFO { get; set; }
        public string FILE_PATH { get; set; }
        public string INSTALLATION_ID { get; set; }
        public bool IS_SILENT { get; set; }
        public string LOGCAT { get; set; }
        public string PACKAGE_NAME { get; set; }
        public string PHONE_MODEL { get; set; }
        public string PRODUCT { get; set; }
        public string STACK_TRACE { get; set; }
        public int TOTAL_MEM_SIZE { get; set; }
        public string USER_APP_START_DATE { get; set; }
        public DateTime USER_CRASH_DATE { get; set; }
        public string USER_EMAIL { get; set; }
    }

به همین سادگی می توانید کرش های برنامه خود را به سمت سرور ارسال کنید.در نظر داشته باشید که اگر میخواهید کانفیگ های دیگری برای ارسال خطا انجام دهید می توانید از این آدرس اطلاعات بیشتری بدست آورید.

استفاده از Glide به جای picasso

یک سالی بود در اندروید برای load عکس ها از picasso استفاده می کردم.مدتی قبل به این نتیجه رسیدم بهتر است به جای picasso از Glide استفاده کنم.قبل استفاده از Glide ، این مقاله را مطالعه کردم.

دلایل مشخصی برای این تصمیم داشتم که سعی کردم آن ها را توضیح دهم:

از نظر نحوه استفاده picasso  با Glide تفاوت چندانی نمی کند و به راحتی کد های شما قابل تبدیل است:
Picasso

Picasso.with(context)
    .load("http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg")
    .into(ivImg);

Glide

Glide.with(context)
    .load("http://inthecheesefactory.com/uploads/source/glidepicasso/cover.jpg")
    .into(ivImg);

به صورت پیش فرض Glide از فرمت RGB_565 استفاده می کند که نسبت به picasso که از ARGB_8888 استفاده می کند دارای کیفیت کمتری است.البته این مقدار را می توان تغییر داد.ولی اگر به میزان مصرف رم توجه کنید متوجه خواهید شد که ارزش دارد از فرمت پایین تر استفاده کنید.

حال اگر تصمیم بگیرید از ARGB_8888 نیز استفاده کنید باز می توان دید که Glide بهتر عمل خواهد کرد:

در زمان تغییر سایز عکس، picasso همه عکس را در رم لود می کند و سپس از طریق GPU سایز عکس را تغییر میدهد.این در حال است که Glide دقیقا سایز مورد نظر را در رم لود می کند و این کار باعث مصرف رم کمتر می شود.

برای کش کردن عکس روی گوشی picasso عکس کامل را در حافظه گوشی ذخیره می کند.این درحالی است که Glide عکس کوچک شده را کش می کند.البته این رفتار را نیز میتوان تغییر داد.

Glide در زمان نمایش عکس ها سریعتر عمل می کند:

Glide

Glide توانایی نمایش گیف را دارد:

برای اطلاعات دقیق تر می توانید به گیت هاب پروژه سر بزنید.