Memahami Context di Android

Bagus Aji Santoso 8 Agustus 2017

Memahami Context di Android

Sebuah Context memberikan akses informasi atas application state. Ia memperbolehkan Activity, Fragment, dan Service untuk mengakses file, gambar, theme/style, dan lokasi direktori eksternal. Context juga memberikan akses ke service yang terpasang di Android yang akan digunakan misalnya untuk layout inflation, keyboard, dan mencari content provider.

Dalam banyak kasus saat kita membutuhkan sebuah context, kita cukup memberikan instance dari activity yang berjalan saat ini. Dalam situasi saat kita ada di dalam objek yang dibuat oleh si activity misalnya adapter atau fragment, kita dapat mengirimkan instance dari activity ke dalam object tersebut. Namun, bila kita berada diluar dari sebuah activity (misalnya service), kita bisa menggunakan "application" context.

Apa kegunaan Context?

Berikut beberapa penggunaan yang membutuhkan objek Context.

Memulai sebuah Intent secara eksplisit

Intent intent = new Intent(context, MyActivity.class);
startActivity(intent);

Saat memulai sebuah Intent secara eksplisit ada dua informasi yang dibutuhkan yaitu:

  • Package name yang mengidentifikasi aplikasi mana yang memiliki si Intent yang memanggil.
  • Class name yang menjelaskan nama komponen tujuan.

Jika akan membuka Intent internal (dari dalam aplikasi yang sama), context dapat di ekstrak secara otomatis melalui context.getPackageName().

Membuat sebuah View

TextView textView =  new TextView(context);

Dalam pembuatan object View di Java (bukan melalui pembacaan id XML) beberapa informasi dibutuhkan:

  • Ukuran dan dimensi layar perangkat untuk mengonversi dp dan sp ke pixels.
  • Atribut untuk style widget.
  • Referensi activity untuk atribut onClick.

Membaca file layout XML

Kita menggunakan context untuk membaca file layout XML melalui LayoutInflater:

LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.my_layout, parent);

Mengirim pesan broadcast

Kita menggunakan context saat mengirimkan atau mendaftarkan sebuah receiver untuk sebuah broadcast:

// Context memegang referensi ke "Looper" utama
// yang akan mengelola sebuah antrian di threat utama aplikasi.
Intent broadcastIntent = new Intent("custom-action");
LocalBroadcastManager.getInstance(context).sendBroadcast(broadcastIntent);

Menerima sebuah System Service

Untuk mengirim notifikasi dari sebuah aplikasi, kita memerlukan system service bernama NotificationManager:

// Object Context dapat mengambil atau memulai system service.
NotificationManager notificationManager = 
    (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

int notificationId = 1;

// Context diperlukan untuk membuat RemoteViews
Notification.Builder builder = 
    new Notification.Builder(context).setContentTitle("custom title");

notificationManager.notify(notificationId, builder.build());

Berikut daftar lengkap seluruh system service yang dapat dipanggil lewat sebuah Context.

Application vs Activity Context

Meski themes dan styles biasanya diaplikasikan di level aplikasi, mereka juga dapat diatur di level Activity. Dengan ini, activity dapat memiliki beberapa themes atau styles dibanding kompoenn aplikasi lain (contoh kita ingin ActionBar di disabled di beberapa halaman). Pembaca akan menyadari di dalam file AndroidManifest.xml terdapat atribut android:theme yang menentukan theme di level aplikasi. Kita juga dapat menambahkan atribut yang sama di level Activity.

<application
       android:allowBackup="true"
       android:icon="@mipmap/ic_launcher"
       android:label="@string/app_name"
       android:theme="@style/AppTheme" >
       <activity
           android:name=".MainActivity"
           android:label="@string/app_name"
           android:theme="@style/MyCustomTheme">

Oleh karena itu, penting untuk mengetahui Application Context dan Activity Context serta siklus hidupnya. Kebanyakan View perlu kita kirimkan sebuah Activity Context agar mereka bisa mendapat akses theme apa, style apa, dan berapa dimensi yang perlu diaplikasikan ke View yang bersangkutan. Apabila tidak ada theme untuk Activity tersebut, maka View akan menggunakan theme default dari aplikasi.

Sebagian besar kebutuhan akan Context dapat dipenuhi dengan memberikan Activity Context. Biasanya perintah this yang mereferensikan instance dari kelas Java dapat memberikan Context yang dibutuhkan dari dalam sebuah Activity. Berikut contoh pesan Toast yang dibuat menggunakan perintah this.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);  
        Toast.makeText(this, "hello", Toast.LENGTH_SHORT).show();
    }
}

Anonymous Function

Saat menggunakan anonymous function yang biasa kita pakai saat mengimplementasi suatu listener, perintah this akan mereferensikan kelas yang dideklarasikan di fungsi tersebut. Dalam kasus ini, kita harus secara eksplisit untuk memanggil this yang ada di MainActivity.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        TextView tvTest = (TextView) findViewById(R.id.abc);
        tvTest.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  Toast.makeText(MainActivity.this, "hello", Toast.LENGTH_SHORT).show();
              }
          });
        }
    }

Apabila kita mengirimkan hanya this ke dalam parameter Toast.makeText() yang pertama, Android Studio akan komplain karena this yang dimaksud adalah dari View.OnClickListener() bukan MainActivity sehingga harus secara eksplisit kita tentukan this dari MainActivity.

Adapter

Array Adapter

Saat membuat sebuah adapter untuk ListView, biasanya metode getContext() akan dipanggil saat proses pemanggilan layout. Metode ini akan menggunakan Context untuk membuat ArrayAdapter.

   if (convertView == null) {
      convertView = 
          LayoutInflater
              .from(getContext())
              .inflate(R.layout.item_user, parent, false);
   }

Saat membuat ArrayAdapter dengan application context, kemungkinan pembaca tidak mendapat theme/style ke layout tersebut. Oleh karena itu, Activity context dibutuhkan disini dan kita mendapatkannya lewat perintah getContext().

RecyclerView Adapter

public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> {

    @Override 
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = 
            LayoutInflater
                .from(parent.getContext())
                .inflate(itemLayout, parent, false);
        
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        // Jika sebuah context dibutuhkan, ia dapat diambil 
        // dari root viewViewHolder.
        Context context = viewHolder.itemView.getContext();

        // Membuat sebuah view secara dinamis dari context yang 
        // didapat.
        if(i == 0) {
            TextView tvMessage = new TextView(context);
            tvMessage.setText("Only displayed for the first item.")

            viewHolder.customViewGroup.addView(tvMessage);
        }
    }

   public static class ViewHolder extends RecyclerView.ViewHolder {
       public FrameLayout customViewGroup;

       public ViewHolder(view imageView) {
           super(imageView);
           
           customViewGroup = (FrameLayout) imageView.findById(R.id.customViewGroup);
       }
   }
}

Apabila ArrayAdapter memerlukan sebuah context untuk dikirim ke constructor-nya, RecyclerView.Adapter tidak. Context yang diperlukan bisa didapatkan dari parent view.

Tag RecyclerView akan selalu menjadi parent view dan mengirimkan dirinya melalui metode RecyclerView.Adapter.onCreateViewHolder.

Jika sebuah context diperlukan diluar onCreateViewHolder, selama ada instance ViewHolder (misalnya didalam onBindViewHolder), jika bisa memanggil context dengan perintah viewHolder.itemView.getContext().

Menghindari Memory Leaks

Application Context umumnya dipakai saat sebuah instace singleton perlu dibuat, misalnya sebuah kelas yang membutuhkan informasi Context agar dapat mengakses system service tapi bakal digunakan berulang kali di beberapa Activity. Karena penggunaan Activity Context untuk penggunaan singleton seperti ini dapat menyebabkan memory leaks karena Activity yang seharusnya sudah tidak aktif dan perlu dibuang dari memory tidak dapat dihapus karena context-nya masih dipakai si singleton). Untuk menghindari hal seperti ini kita mesti menggunakan Application Context.

pubic class CustomManager {
    private static CustomManager sInstance;

    public static CustomManager getInstance(Context context) {
        if (sInstance == null) {
			// Kelas ini memegang referensi context yang dapat
			// berupa Activity atau Service
            sInstance = new CustomManager(context);
        }

        return sInstance;
    }

    private Context mContext;

    private CustomManager(Context context) {
        mContext = context;
    }
}

Untuk menghindari memory leaks jangan pernah mereferensikan context diluar siklus hidupnya. Periksa setiap background thread, pending handler, atau inner class yang mungkin masih memegang salah satu object context.

Cara paling baik untuk menyimpan application context ialah melalui CustomManager.getInstance() seperti pada contoh. Application context-nya adalah sebuah singleton dan akan bergantung pada siklus hidup si aplikasinya langsung, sehingga aman untuk menyimpan dan mereferensikannya.

Gunakan instance application context saat sebuah context dibutuhkan diluar siklus hidup sebuah komponen atau Activity atau saat komponen tersebut tidak tergantung pada siklus hidup context yang diterimanya.

public static CustomManager getInstance(Context context) {
    if (sInstance == null) {

        // Saat menyimpan referensi ke context, gunakan application context.
        // Jangan menyimpan context-nya langsung karena dapat berupa sebuah komponen.
        sInstance = new CustomManager(context.getApplicationContext());
    }

    return sInstance;
}

Sumber: Using Context