Context trong Android là gì? Bạn đã hiểu đúng về nó chưa? – VNTALKING

Như bạn đã biết, dù cho chúng ta truy cập tới một View hoặc các tài nguyên hệ thống (Asset), phần lớn chúng ta đều tham chiếu đến một Context. Hôm nay mình muốn trình bày với các bạn về Context trong Android.

Bạn có công nhận là bạn gọi rất nhiều đến Context trong ứng dụng không ? Vậy bạn đã hiểu thực chất của Content trong Android là gì chưa ? Và có khi nào bạn bị cảnh báo nhắc nhở là dùng Context không đúng sẽ làm ứng dụng bị Memory Leak chưa ?
Bài viết này, tất cả chúng ta sẽ cùng đi tìm cội nguồn của Context nhé !

Context trong Android là gì?

Context trong Android là gì ?

Theo documentation của Google :

Context is an interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.

Tạm dịch :

Context là một interface chứa thông tin toàn cục về môi trường ứng dụng. Đây là một lớp trừu tượng được triển khai bởi hệ thống Android. Nó cho phép truy cập đến các tài nguyên và các lớp ứng dụng cụ thể, cũng như gọi đến các tác vụ trên mức ứng dụng như khởi chạy các activity, gửi và nhận intents, v.v..

Nói cách khác, tất cả chúng ta đều đã biết và sử dụng nó, trong nhiều cách khác nhau. Nhưng thường là sai cách hoặc đều không chăm sóc đến những việc Garbage Collector ( GC – trình dọn rác của JVM ) sẽ quét dọn context như nào v.v.. dẫn đến việc bị leak memory …
Cứ bình tĩnh, tất cả chúng ta sẽ liên tục tìm hiểu và khám phá bên dưới nhé

Có bao nhiêu cách để gọi Context trong Android

Có những cách nào để có thể get được một context?

Theo kinh nghiệm tay nghề bản thân thì có một cách gọi như sau :

  • this / getActivity()
  • getContext()
  • getBaseContext()
  • getApplicationContext()

this ở đây chính là Activity hiện tại. Ví dụ, hàm hiển thị Toast quen thuộc:

Toast.makeText(this, "Boom!", Toast.LENGTH_LONG).show();

getContext ( ) là một hàm của lớp View, sẽ nhận được context hiện tại của View đó .
getBaseContext ( ) là một trường hợp đặc biệt quan trọng. Cách gọi này ít thông dụng nhưng không có nghĩa là trọn vẹn vô dụng. Nó chỉ sử dụng khi bạn biết rõ nó là gì, và có nguyên do chính đáng để sử dụng. Đơn cử như bạn muốn override một Context bằng một context khác .
getApplicationContext ( ) được sử dụng ở những nơi mà bạn không chăm sóc hoặc không cần phải truy vấn đến context của activity. Những gì bạn muốn là thông tin của context trong ứng dụng của bạn .

Lưu ý này: Bạn phải rất thận trọng để không giữ context lâu hơn cần thiết. Đặc biệt là với các activity. Rất tiếc là tất cả chúng ta đều phạm lỗi này bằng cách tạo ra các anonymous inner classes với một tham chiếu mạnh (strong reference) đến Context trong Android.

Mình lấy ví dụ điển hiển đó là gọi Context trong AsyncTask.

AsyncTasks là công cụ mà việc thực thi không dừng lại sau khi activity bị hủy. Do đó, garbage collector không thể thu hồi nó khi Activity bị hủy. Và nguy cơ Memory Leak sẽ xảy ra rất lớn.

Vậy phải làm thế nào đây ?

Như ví dụ bên dưới, mình sử dụng Handler để đảm bảo context của Activity không bị leak. Nhưng cách này khá tốn effort, đặc biệt khi viết một ứng dụng phức tạp. (Đọc thêm bài về Handler trong Android nhé)

private static final Handler handler = new Handler();
private final Runnable messageTask = 
        new SetMessageTask(this, "This is the message!");

private void postTask() {
    handler.postDelayed(messageTask, 8000);
}

private static class SetMessageTask implements Runnable {
    private final WeakReference activity;
    pivate final String message;

    SetMessageTask(MainActivity activity, String message) {
        this.activity = new WeakReference<>(activity);
        this.message = message;
   }

    @Override
    public void run() {
    if (message == null || message.isEmpty()) {
        Log.e("SetMessageRunnable", "The message is null!");
        return;
    }

    MainActivity activity = (MainActivity) this.activity.get();
    if (activity == null) {
    Log.w("SetMessageRunnable",
 		"Activity is not available anymore.");
       return;
    }

    // do something with activity instance here...
    }
}

Sử dụng Context trong Fragment

Với việc bổ sung fragments, mọi thứ trở nên phức tạp hơn. Bởi vì vòng đời của fragments kết nối tới vòng đời của các activity. Điều này gây ra nhiều lỗi về state và transactions, cũng như việc quản lý stack của ứng dụng.

Cá nhân mình, đã tận mắt chứng kiến lỗi crash java.lang. IllegalStateException Crash : Can not perform this action after onSaveInstanceState rất nhiều lần. Lỗi này xảy ra do việc tạo những transaction mới sau khi lưu trạng thái hoạt động giải trí, hoặc trước khi activity được tái tạo lại .
Đôi khi bạn nghĩ transactions của fragments ở ngoài hàm onCreate ( ) sẽ gây ra một exception. Điều này không đúng. Bởi vì bạn cũng hoàn toàn có thể thực thi một transactions trong onPostResume ( ) hoặc trong onResumeFragments ( ) .
Tuy nhiên, đó chỉ là ba nơi bạn hoàn toàn có thể gọi, điều này có vẻ như hơi hạn chế nhỉ .
Context trong Android

Do tài nguyên điện thoại có hạn, nên activity có thể bị hủy bất cứ lúc nào bởi hệ thống để giải phóng tài nguyên. Đó là lý do tại sao bạn không nên dựa quá nhiều vào context và tính khả dụng của nó.

Quy tắc sử dụng Context trong Android

Đôi khi, có những trường hợp rất “ nực cười ” trong ứng dụng. ví dụ, việc trấn áp hiển thị view và load data cho view trong cùng một hàm ( bạn không nên làm điều này nhé, chỉ ví dụ thui ! ) .
Khi mà việc trộn lẫn giữa khai báo view và tải tài liệu cho view đó cũng trong 1 hàm. Điều này làm mọi thứ phụ thuộc vào lẫn nhau dẫn đến khó trấn áp .
Bạn hoàn toàn có thể bị sa đà vào việc code mọi thứ trong onCreateView ( ) … Và sự Open của Fragments chỉ gây ra những yếu tố thêm trầm trọng, đặc biệt quan trọng là việc sử dụng Context .

Một phần vì Context không phải lúc cũng sẵn sàng để gọi được. Chỉ trong onActivityCreated() hoặc onAttach() chúng ta mới chắc chắn về tính khả dụng của Context.

Ở hình bên dưới, bạn hoàn toàn có thể thấy MainActivity đã bị memory leak. Đây là tác dụng mình sử dụng thư viện LeakCanary để phát hiện memory leak
Context trong android

6 Quy tắc sử dụng Context trong Android

Tóm lại, để hạn chế để xảy ra lỗi memory leak khi sử dụng Context, mình đúc kết được một số quy tắc như sau:

  • Sử dụng getContext() hoặc Activity.this khi cần xử lý đến các Views nằm trên activity.
  • Sử dụng getApplicationContext() nếu bạn cần context ở cấp ứng dụng, không phù hợp với bất kỳ view/ activity nào. ví dụ: sử dụng hàm này với BroadcastReceiver hoặc Service)
  • Không sử dụng getBaseContext(). Khi mà không thực sự hiểu thì tốt nhất tìm cách khác an toàn hơn
  • Sử dụng WeakReference nếu bạn cần truy cập đến context từ bên trong threads.
  • Không tham chiếu đến context của một activity từ một activity khác. Tuyệt đối không sử dụng context như một biến static.
  •  Ở trong fragment, gán một context để sử dụng ở trong hàm onAttach(Context context)

Như vậy, tất cả chúng ta đã hiểu rõ về Context trong Android rồi đúng không ? Và mình hy vọng với những quy tắc trên, bạn sẽ không còn lo ngại về yếu tố memory leak khi sử dụng context nữa
Nếu thấy bài viết hay thì đừng quên san sẻ cho mọi người cùng đọc nhé