Chào mừng các bạn đã đến với bài học Android thứ 28, bài học về vòng đời Activity. Đây là bài học trong chuỗi bài viết về lập trình ứng dụng Android bằng Java của Yellow Code Books.
Qua bài học hôm trước, bạn đã thấy mình cố gắng trình bày các kịch bản mà các Activity, hay chính là các màn hình, được hệ thống quản lý như thế nào. Qua đó bạn cũng đã biết khi nào một màn hình vẫn còn được giữ lại trong hệ thống để phòng trường hợp người dùng bất ngờ quay lại. Hoặc khi nào thì hệ thống hủy đi một màn hình.
Bài học hôm nay chúng ta sẽ chỉ xem xét khía cạnh đời sống của duy nhất một màn hình. Để xem dưới sự tác động bởi hệ thống, thì các trạng thái của em nó sẽ phải trải qua là gì. Và với mỗi trạng thái như vậy bạn sẽ biết cách tác động đến ứng dụng để thực hiện một số tác vụ thích hợp nhất theo từng trạng thái như thế nào.
Nghe qua có vẻ hay đúng không. Mời các bạn cùng đến với bài học Android tiếp theo này.
Vòng Đời Activity Là Gì?
Bạn đều biết, ngoài đời thực, vòng đời của một sinh vật nào đó sẽ được tính từ lúc nó sinh ra cho đến lúc chết đi. Thì Activity cũng vậy. Vòng đời Activity sẽ được chúng ta xem xét khi nó được khởi tạo ra (creating) bởi hệ thống, trải qua các trạng thái như dừng lại (stopping), tiếp tục (resuming),… cho đến lúc bị hủy (destroying) khỏi hệ thống. Sở dĩ có nhiều trạng thái như vậy là vì các tương tác từ người dùng mà ra, như khi họ mở một màn hình, khi họ chuyển sang màn hình khác, khi họ chuyển sang một ứng dụng khác, hay nghe một cuộc gọi đến,… Nếu chúng ta nắm được vòng đời và các trạng thái này của Activity, chúng ta sẽ xây dựng ứng dụng trở nên thông minh hơn, và dĩ nhiên, ít lỗi hơn.
Mình giả sử bạn đang xây dựng một ứng dụng phát video. Nếu người dùng đang xem một video mà ứng dụng của bạn đang phát, bỗng đột ngột chuyển sang ứng dụng khác, hay có cuộc gọi đến. Thì lập tức ứng dụng đó phải biết và lưu lại mọi trạng thái của nó, bao gồm cả đoạn video mà người dùng còn đang xem dang dở. Sau đó các tài nguyên của hệ thống nếu có phải được giải phóng tạm thời. Một lúc sau, nếu người dùng quay lại ứng dụng của bạn, họ sẽ được xem tiếp đoạn video lúc nãy, chứ không phải bị reset lại từ đầu. Bạn có thể thấy khi này ứng dụng của bạn rất thông minh. Nhưng tất cả những sự thông minh đó là do bạn chuẩn bị cả đấy, hệ thống chỉ giúp thực thi các trạng thái khi có sự thay đổi. Lát nữa chúng ta cùng làm quen các trạng thái và cách “lắng nghe” các trạng thái này.
Sơ Đồ Minh Họa Vòng Đời Activity
Sơ đồ thần thánh sau là minh họa rõ ràng và đầy đủ nhất về vòng đời Activity.
Như đã nói, sơ đồ này chính là mô phỏng vòng đời Activity. Mỗi Activity trong một ứng dụng sẽ có một vòng đời khác nhau. Nhưng chúng đều theo chuẩn của sơ đồ này cả.
Bạn hãy chú ý các phương thức được mô tả trong sơ đồ này, bao gồm onCreate(), onStart(), onRestart(), onResume(), onPause(), onStop() và onDestroy(). Các phương thức này được gọi là các lời gọi về, bạn chỉ cần nhớ nghĩa tiếng Anh của chúng là callback. Các callback chính là các phương thức được bạn override lại từ các phương thức sẵn có của lớp cha, để khi mà hệ thống cần, hệ thống sẽ gọi đến các phương thức đó trong ứng dụng của bạn. Bạn nhớ lại đi, với việc code cho TourNote từ trước tới giờ, bạn đã biết đến callback nào trong các callback mình liệt kê ở trên rồi?
Trước khi làm quen với các callback, chúng ta hãy diễn tả sơ về sơ đồ trên.
Mô Tả Sơ Đồ
Sơ đồ bắt đầu từ khi Activity launched, tức là khi Activity được kích hoạt, và được hệ thống để vào BackStack. Sau khi kích hoạt, lần lượt các callback onCreate(), onStart(), onResume() sẽ được hệ thống gọi đến.
Sau khi gọi đến các callback trên, thì Activity mới chính thức được xem là đang chạy (Activity running).
Lúc này, nếu có bất kỳ Activity nào khác chiếm quyền hiển thị, thì Activity hiện tại sẽ rơi vào trạng thái onPause(). Nếu cái sự hiển thị của Activity khác làm cho Activity mà chúng ta đang nói đến không còn nhìn thấy nữa thì onStop() sẽ được gọi tiếp theo nữa. Lát nữa khi đi vào các chi tiết dưới đây bạn sẽ hiểu sự khác nhau giữa hai callback này thôi.
Nếu Acvitity đã vào onPause() rồi, tức là đang bị Activity khác đè lên, mà người dùng sau đó quay về lại Activity cũ, thì onResume() được gọi. Còn nếu Activity đã vào onStop() rồi, mà người dùng quay về lại Activity cũ thì onRestart() được gọi.
Trong cả hai trường hợp Activity rơi vào onPause() hoặc onStop(), nó sẽ rất dễ bị hệ thống thu hồi (tức là hủy luôn í) để giải phóng tài nguyên, khi này nếu quay lại Activity cũ, onCreate() sẽ được gọi chứ không phải onResume() hay onRestart() đâu nhé.
Và cuối cùng, nếu một Activity bị hủy một cách có chủ đích, chẳng hạn như người dùng nhấn nút Back ở System Bar, hay hàm finish() được gọi,… thì onDestroy() sẽ được kích hoạt và Activity kết thúc vòng đời của nó.
Nếu các mô tả trên đây lan man quá thì bạn có thể chỉ cần nhớ đến bốn trạng thái chính dưới đây.
Các Trạng Thái Chính Trong Vòng Đời Activity
Hoạt Động (Active)
Khi Activity được kích hoạt, và được hệ thống để vào BackStack, nó sẽ bước vào trạng thái active. Với trạng thái active, người dùng hoàn toàn có thể nhìn thấy và tương tác với Activity của ứng dụng.
Tạm Dừng (Pause)
Trạng thái này khá đặc biệt. Trạng thái tạm dừng. Như bạn đã làm quen trên kia, trạng thái này xảy ra khi mà Activity của bạn vẫn đang chạy, người dùng vẫn nhìn thấy, nhưng Activity khi này lại bị che một phần bởi một thành phần nào đó. Chẳng hạn như khi bị một dialog đè lên (bạn sẽ được kiểm chứng khi học qua bài về dialog sau).
Bạn nhớ nhé, cái sự che Activity này không phải hoàn toàn. Chính vì vậy mà Activity đó tuy được người dùng nhìn thấy nhưng không tương tác được.
Dừng (Stop)
Trạng thái này khá giống với trạng thái tạm dừng trên kia. Nhưng khi này Activity bị che khuất hoàn toàn bởi một thành phần giao diện nào đó, hoặc bởi một ứng dụng khác. Và tất nhiên lúc này người dùng không thể nhìn thấy Activity của bạn được nữa.
Hành động mà khi người dùng nhấn nút Home ở System Bar để đưa ứng dụng của bạn về background, cũng khiến Activity đang hiển thị trong ứng dụng rơi vào trạng thái dừng này. Bạn sẽ biết khi đi đến bài thực hành dưới đây.
Chết (Dead)
Nếu Activity được lấy ra khỏi BackStack, chúng sẽ bị hủy và vào trạng thái này. Trường hợp này xảy ra khi user nhấn nút Back ở System Bar để thoát một Activity. Hoặc lời gọi hàm finish() từ một Activity để “giết chính nó”. Cũng có khi ứng dụng ở trạng thái background quá lâu, hệ thống có thể sẽ thu hồi tài nguyên bằng cách dừng hẳn các Activity trong ứng dụng, làm cho tất cả các Activity đều vào trạng thái này.
Khi vào trạng thái dead, Activity sẽ kết thúc vòng đời “trôi nổi” của nó.
Những ý trên giúp bạn nắm được tổng quan các trạng thái mà một Activity có thể trải qua. Chúng ta cùng đi gần đến code hơn bằng cách nói đến công dụng của từng callback.
Làm Quen Với Các Callback
Sau đây là tất cả các callback mà bạn nên biết. Bạn không nhất thiết phải hiện thực cho tất cả các callback này đâu nhé. Chỉ cần dùng đủ thôi, bài thực hành bên dưới sẽ phần nào giúp bạn giải đáp ý này.
onCreate()
Theo mình thì đây là callback tối thiểu trong một Activity mà bạn phải hiện thực, theo mình thôi nhé, bạn có thể bỏ qua nó mà không có báo lỗi gì.
Callback onCreate() này được gọi khá sớm, ngay khi Activity được kích hoạt, và thậm chí người dùng còn chưa nhìn thấy gì của Activity cả, thì callback này đã được gọi rồi. Ngoài ra thì bạn nên biết là callback này chỉ được gọi một lần duy nhất khi Activity được khởi tạo. Nó có thể được gọi lại nếu hệ thống xóa Activity này đi để lấy lại tài nguyên của hệ thống, nhưng rất hiếm khi xảy ra. Và nó còn có thể được gọi lại nếu bạn xoay màn hình ngang/dọc.
Do đặc tính được gọi khá sớm và chỉ được gọi một lần duy nhất trong vòng đời của nó như vậy, nên bạn sẽ tận dụng để load giao diện cho Activity ở giai đoạn này, thông qua phương thức setContentView() mà bạn đã biết từ những ngày đầu tiên. Ngoài giao diện ra, bạn có thể khởi tạo các logic nào đó chỉ chạy một lần ban đầu, như các lời gọi API, load database, tạo item list, tạo Navigation Drawer, và nhiều logic khác mà bạn đã từng làm quen ở callback này từ các bài thực hành trước.
Tham số savedInstanceState được truyền vào phương thức này sẽ được mình nói đến ở bài viết về lưu trữ trong Android sau nhé.
onStart()
Sau khi gọi đến onCreate(), hệ thống sẽ gọi đến onStart(). Hoặc hệ thống cũng sẽ gọi lại onStart() sau khi gọi onRestart() nếu trước đó nó bị che khuất bởi Activity nào khác (một màn hình khác hoặc một ứng dụng khác) che hoàn toàn và rơi vào onStop().
Khi hệ thống gọi đến callback này thì Activity chuẩn bị (có nghĩa là chưa) được nhìn thấy bởi người dùng và tương tác với người dùng. Bởi đặc tính này mà onStart() ít được dùng đến.
onResume()
Khi hệ thống gọi đến callback này thì bạn yên tâm rằng người dùng đã nhìn thấy và đã tương tác được với giao diện.
onResume() được gọi khi Activity được khởi tạo rồi và bước qua onStart() trên kia. Hoặc khi Activity bị một giao diện nào khác che đi một phần (hoặc toàn phần), rồi sau đó quay lại Activity hiện tại. Bạn có thể thấy rằng callback này được gọi rất nhiều lần trong một vòng đời của nó.
Chính đặc điểm này của onResume() mà bạn có thể tận dụng để quay lại tác vụ mà người dùng đang bị dang dở khi onPause() (được nói đến dưới đây) được gọi.
Chẳng hạn như bạn đang soạn nội dung cho TourNote, mà có cuộc gọi đến, bạn sẽ lưu tạm nội dung này khi callback onPause(), để rồi khi onResume() được gọi lại sau đó khi người dùng kết thúc cuộc gọi và quay lại TourNote, bạn sẽ khôi phục nội dung đó để người dùng tiếp tục sử dụng TourNote như chưa có bất kỳ gián đoạn nào.
onPause()
Thông thường nếu có một thành phần nào đó che Activity hiện tại mà người dùng vẫn nhìn thấy Activity đó (nhìn thấy chứ không tương tác được). Chẳng hạn một popup hiện lên trên Activity. Thì onPause() của Activity sẽ được gọi. Sau này khi người dùng quay lại Activity thì onResume() sẽ được gọi.
Bạn có thể tưởng tượng rằng onPause() cũng sẽ được gọi khá nhiều lần trong một vòng đời Activity. Theo như Google thì onPause() được gọi đến khá nhanh, nếu bạn muốn lưu trữ dữ liệu như mình nói trên kia, thì nên lưu những gì nhanh gọn lẹ thôi. Nếu bạn muốn lưu trữ các dữ liệu nặng, hoặc gọi API kết nối server chỗ này, nhiều khả năng ứng dụng sẽ không kịp thực hiện. Do đó, thay vì làm các thao tác nặng nề ở onPause(), bạn có thể cân nhắc gọi chúng ở onStop().
onStop()
Như mình có nói. onStop() được gọi khi Activity không còn được nhìn thấy nữa, có thể một màn hình nào khác che lên hoàn toàn, có thể một ứng dụng nào đó vào foreground, hoặc người dùng nhấn nút Home để về màn hình chính.
Bạn có thể tận dụng onStop() để lưu trữ dữ liệu ứng dụng. Hoặc để giải phóng các tài nguyên đang dùng. Ngưng các API còn đang gọi dang dở.
Tuy nhiên khi onStop() được gọi không phải là lúc chúng ta cũng nói lời tạm biệt Activity. Như mình đã nói, người dùng hoàn toàn có thể quay lại sử dụng Activity sau đó mà không cần phải khởi động lại Activity, khi này thì phương thức onRestart() và onStart() được gọi kế tiếp nhau.
onDestroy()
Trước khi Activity “chết”, hệ thống cho nó cơ hội để nói lời trăn trối, nó sẽ giúp gọi đến callback onDestroy() này của Activity.
Bạn có thể tận dụng callback này để giải phóng các tài nguyên hệ thống mà ở onStop() bạn chưa gọi đến.
Vòng đời của một Activity kết thúc ở đây.
Thực Hành Tìm Hiểu Vòng Đời Của MainActivity
Bài thực hành hôm nay sẽ không có code đẩy lên GitHub. Chúng ta chỉ dùng log để ghi lại các trạng thái của MainActivity. Qua bài thực hành hôm nay, chắc chắn bạn sẽ hiểu vòng đời Activity một cách rõ ràng hơn, chứ không lung tung như lý thuyết trên kia nữa.
Nào, để bắt đầu thực hành, mình mời bạn mở project TourNote lên. Mở MainActivity.java lên. Chúng ta sẽ override hết các callback đã nói trên đây, in log ra ở từng callback để xem chúng được gọi khi nào nhé.
Code của MainActivity.java như sau, code tuy nhiều nhưng là code của mấy bài trước, bạn chỉ cần chú ý các dòng được tô sáng thôi nhé.
public class MainActivity extends AppCompatActivity { private DrawerLayout drawerLayout; private ActionBarDrawerToggle drawerToggle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); drawerLayout = (DrawerLayout) findViewById(R.id.activity_main_drawer); drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawerLayout.addDrawerListener(drawerToggle); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true); Log.d("MainActivity Lifecycle", "===== onCreate ====="); } @Override protected void onStart() { super.onStart(); Log.d("MainActivity Lifecycle", "===== onStart ====="); } @Override protected void onRestart() { super.onRestart(); Log.d("MainActivity Lifecycle", "===== onRestart ====="); } @Override protected void onResume() { super.onResume(); Log.d("MainActivity Lifecycle", "===== onResume ====="); } @Override protected void onPause() { super.onPause(); Log.d("MainActivity Lifecycle", "===== onPause ====="); } @Override protected void onStop() { super.onStop(); Log.d("MainActivity Lifecycle", "===== onStop ====="); } @Override protected void onDestroy() { super.onDestroy(); Log.d("MainActivity Lifecycle", "===== onDestroy ====="); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. drawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); drawerToggle.onConfigurationChanged(newConfig); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_actions, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { if(drawerToggle.onOptionsItemSelected(item)) { return true; } switch (item.getItemId()) { case R.id.search: Toast.makeText(this, "Search button selected", Toast.LENGTH_SHORT).show(); return true; case R.id.about: Intent intent = new Intent(this, ContactActivity.class); startActivity(intent); return true; case R.id.help: intent = new Intent(this, ContactActivity.class); startActivity(intent); return true; } return super.onOptionsItemSelected(item); } }
Nào, giờ chúng ta cùng xem từng trường hợp cụ thể nào.
Khởi Chạy Ứng Dụng
Khi MainActivity được khởi chạy, bạn có thể thấy các dòng log của các callback onCreate(), onStart() và onResume() được gọi đến và hiển thị ở Android Monitor.
Khởi Chạy ContactActivity
Từ MainActivity, bạn hãy nhấn chọn bất kỳ meu item của ActionBar để đi đến ContactActivity. Bạn có thể thấy các callback onPause() và onStop() của MainActivity được gọi. Vì khi này ContactActivity đã che hết màn hình của MainActivity nên hệ thống không dừng ở onPause() mà gọi tiếp onStop() của MainActivity là vậy.
Ý tưởng: bạn có thể thử xây dựng thêm các callback ở ContactActivity và thử xem log của cả hai Activity để biết được nhiều thông tin hơn nhé.
Quay Về MainActivity Từ ContactActivity
Tiếp theo bước trên đây. Nếu bạn nhấn nút Back (từ ActionBar hoặc từ System Bar) để về lại MainActivity. Bạn sẽ thấy các callback onRestart(), onStart() và onResume() của MainActivity được gọi.
Từ MainActivity Nhấn Home
Lần này bạn thử nhấn Home để đưa ứng dụng về background, chuyện gì sẽ xảy ra với MainActivity? Cũng như bạn đi từ MainActivity đến Activity khác thôi, màn hình chính cũng là một màn hình nào đó che hoàn toàn MainActivity. Nên onPause() và onStop() vẫn được gọi y như trên kia.
Từ Màn Hình Chính Quay Về MainActivity
Lần này bạn hãy nhấn nút Overview từ System Bar rồi chọn lại ứng dụng TourNote, bạn sẽ thấy MainActivity xuất hiện lại với các callback onRestart(), onStart() và onResumr() y như thao tác quay về từ ContactActivity trên kia.
Từ MainActivity Nhấn Back
Lần này bạn đoán là MainActivity bị hủy đúng không? Bạn đã đoán đúng. Các callback onPause(), onStop() và cả onDestroy() được gọi. Thật thú vị.
Tất nhiên, sau bước này, dù bạn có nhìn thấy như TourNote vẫn đang sống. Nhưng khi quay lại TourNote lần nữa, MainActivity sẽ phải khởi tạo lại qua các callback onCreate(), onStart(), onResume() như ban đầu.
Bạn có thể thử nghiệm tiếp với việc xoay màn hình ứng dụng, để xem Activity lúc đó bị hủy và khởi tạo lại như thế nào nhé.
Bạn vừa cùng mình xem qua một kiến thức nhẹ nhàng và một bài thực hành nhẹ nhàng để hiểu rõ vòng đời Activity.
Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:
– Đánh giá 5 sao bên dưới mỗi bài nếu thấy thích.
– Comment bên dưới mỗi bài nếu có thắc mắc.
– Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
– Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.
Bài Kế Tiếp
Chúng ta đã biết cách kích hoạt một Activity, và quay về Activity trước đó rồi. Vậy có khi nào bạn thắc mắc rằng nếu các Activity có nhu cầu truyền dữ liệu cho nhau, thì chúng sẽ dùng chức năng gì? Trong trường hợp này biến static có giúp ích gì không? Mời bạn cùng đọc phần kế tiếp nhé.