4 component chính trong Android là gì

Có lẽ gần đây các Android dev chúng ta thường nghe nói (và/hoặc đã làm) về các mô hình kiến trúc (Architecture Pattern) của Android. Google cũng đã xây dựng hẳn 1 github repo dành để giới thiệu (và hướng dẫn) về các mô hình kiến trúc này. Tuy nhiên, việc xây dựng các kiến trúc đó mới chỉ dựa trên nền tảng Java đơn thuần, trong khi đó với 1 app Android ta có nhiều việc cần phải làm hơn ví dụ như việc quản lý lifecycle của app, Activity hay Fragment. Hiểu được điều đó, Google đã xây dựng lên một tập hợp các lib dành riêng cho việc xây dựng kiến trúc này, với tên gọi Android Architecture Components. Hãy bắt đầu với lời giới thiệu của Google:

A new collection of libraries that help you design robust, testable, and maintainable apps. Start with classes for managing your UI component lifecycle and handling data persistence.

  • Note: Đây mới chỉ là phiên bản preview, nghĩa là bộ lib này vẫn đang trong quá trình phát triển và chưa chính thức được release. Khuyến cáo chúng ta chỉ nên tìm hiểu trước chứ không nên áp dụng ngay vì có thể sẽ còn nhiều thay đổi trong tương lai.

Trong bài đầu tiên này ta sẽ tìm hiểu bắt đầu từ App Architecture.

Các vấn đề thường gặp của các developer

Không giống như các máy tính để bàn truyền thống, trong đa số trường hợp, có một điểm vào từ lối tắt của launcher và chạy như một quá trình đơn khối, các ứng dụng Android có cấu trúc phức tạp hơn nhiều. Một ứng dụng Android điển hình được xây dựng trên nhiều thành phần của ứng dụng , bao gồm các Activity, Fragment, Service, Content Provider và Broastcast Receiver.

Hầu hết các thành phần ứng dụng này được khai báo trong tệp Manifest của ứng dụng được sử dụng bởi Hệ điều hành Android để quyết định làm thế nào để tích hợp ứng dụng của bạn vào trải nghiệm người dùng tổng thể với các thiết bị của họ. Mặc dù, như đã đề cập trước đó, một ứng dụng dành cho máy tính để bàn thường chạy theo quy trình khối, ứng dụng Android được viết đúng cần phải linh hoạt hơn nhiều khi người dùng trải qua các ứng dụng khác nhau trên thiết bị của họ, liên tục chuyển đổi các luồng và tác vụ.

Ví dụ: hãy xem xét điều gì sẽ xảy ra khi bạn chia sẻ ảnh trong ứng dụng mạng xã hội ưa thích của bạn. Ứng dụng này kích hoạt Camera Intent mà hệ điều hành Android khởi chạy ứng dụng máy ảnh để xử lý yêu cầu. Tại thời điểm này, người dùng rời ứng dụng mạng xã hội nhưng trải nghiệm của họ liền mạch. Mặt khác, ứng dụng camera có thể kích hoạt các Intent khác, chẳng hạn như khởi chạy trình chọn tệp, có thể khởi chạy một ứng dụng khác. Cuối cùng, người dùng quay lại ứng dụng mạng xã hội và chia sẻ ảnh. Ngoài ra, người dùng có thể bị gián đoạn bằng một cuộc điện thoại tại bất kỳ điểm nào trong quá trình này và quay lại để chia sẻ ảnh sau khi kết thúc cuộc gọi điện thoại.

Trong Android, hành vi ứng dụng kiểu này rất phổ biến, vì vậy ứng dụng của bạn phải xử lý đúng các luồng này. Hãy nhớ rằng các thiết bị di động là tài nguyên bị ràng buộc, do đó bất cứ lúc nào hệ điều hành có thể cần phải kill một số ứng dụng để nhường chỗ cho các ứng dụng mới.

Tâm điểm của tất cả các điều này là các thành phần ứng dụng của bạn có thể được khởi chạy riêng lẻ và không theo thứ tự và có thể bị destroy bất cứ lúc nào bởi người dùng hoặc hệ thống. Vì các thành phần của ứng dụng là không phù hợp và vòng đời của chúng (khi chúng được tạo và hủy) không thuộc sự kiểm soát của bạn, bạn không nên lưu trữ dữ liệu ứng dụng hoặc trạng thái trong các thành phần ứng dụng của bạn và các thành phần ứng dụng của bạn không nên phụ thuộc lẫn nhau.

Nguyên tắc kiến trúc chung

Nếu bạn không thể sử dụng các thành phần ứng dụng để lưu trữ dữ liệu ứng dụng và trạng thái, làm thế nào để áp dụng được cấu trúc?

Điều quan trọng nhất bạn nên tập trung vào là sự tách biệt các mối quan tâm trong ứng dụng của bạn. Một sai lầm phổ biến là bạn thường viết tất cả code của bạn trong một Activity hoặc một Fragment . Bất kỳ code nào không xử lý giao diện người dùng hoặc tương tác hệ điều hành không được để trong các lớp này. Giữ cho chúng càng gọn gàng càng tốt, nó sẽ cho phép bạn tránh được nhiều vấn đề liên quan đến vòng đời. Đừng quên rằng bạn không sở hữu những class này, chúng chỉ là những lớp gắn kết để thể hiện contract giữa hệ điều hành và ứng dụng của bạn. Hệ điều hành Android có thể destroy chúng bất cứ lúc nào dựa trên tương tác của người dùng hoặc các yếu tố khác như bộ nhớ thấp. Tốt nhất là giảm thiểu sự phụ thuộc vào chúng để cung cấp trải nghiệm người dùng vững chắc.

Nguyên tắc quan trọng thứ hai là bạn nên điều khiển giao diện người dùng của bạn từ một Model, tốt hơn là một Model bền bỉ. Tính bền bỉ là lý tưởng vì hai lý do: người dùng của bạn sẽ không bị mất dữ liệu nếu hệ điều hành hủy ứng dụng của bạn để giải phóng tài nguyên và ứng dụng của bạn sẽ tiếp tục hoạt động ngay cả khi kết nối mạng yếu hoặc không có kết nối. Các Model là các thành phần chịu trách nhiệm xử lý dữ liệu cho ứng dụng. Chúng độc lập với các View và các app component trong ứng dụng của bạn, do đó chúng bị cô lập khỏi các vấn đề về vòng đời của các thành phần đó. Giữ code giao diện đơn giản và không có logic của ứng dụng làm cho việc quản lý trở nên dễ dàng hơn. Ứng dụng của bạn dựa trên các lớp Model với trách nhiệm quản lý dữ liệu được xác định rõ ràng sẽ làm cho chúng có thể test được và ứng dụng của bạn nhất quán.

Nguyên tắc hướng dẫn

Lập trình là một lĩnh vực sáng tạo, và xây dựng ứng dụng Android không phải là ngoại lệ. Có nhiều cách để giải quyết vấn đề, có thể là liên lạc dữ liệu giữa các Activity hoặc các Fragment, lấy dữ liệu từ xa và duy trì nó ở chế độ ngoại tuyến hoặc bất kỳ một số kịch bản phổ biến khác mà các ứng dụng thường gặp phải.

Mặc dù các khuyến nghị dưới đây là không bắt buộc, nhưng theo kinh nghiệm của Google, việc làm theo chúng sẽ làm cho code của bạn mạnh mẽ hơn, có thể test và maintain trong thời gian dài.

  • Các entry point bạn xác định trong Manifest của bạn - các Activity, Service, Broadcast Receiver, v.v ... không phải là nguồn dữ liệu. Thay vào đó, họ chỉ nên phối hợp các tập con của dữ liệu có liên quan đến entry point đó. Vì mỗi thành phần ứng dụng khá ngắn, tùy thuộc vào sự tương tác của người dùng với thiết bị của họ và tình trạng tổng thể hiện tại của hệ điều hành, bạn không muốn bất kỳ entry point nào là nguồn dữ liệu.
  • Đừng chần chừ trong việc tạo ra ranh giới trách nhiệm rõ ràng giữa các mô đun khác nhau của ứng dụng. Ví dụ: không lây lan code tải dữ liệu từ mạng qua nhiều lớp hoặc gói trong cơ sở mã của bạn. Tương tự, đừng làm những thứ không liên quan đến trách nhiệm - chẳng hạn như lưu trữ dữ liệu và ràng buộc dữ liệu - vào cùng một lớp.
  • Hãy phơi bày càng ít càng tốt từ mỗi mô-đun. Không bị cám dỗ để tạo ra "chỉ một" lối tắt mà phơi bày chi tiết thực hiện nội bộ từ một mô-đun. Bạn có thể đạt được một chút thời gian trong ngắn hạn, nhưng bạn sẽ phải trả nợ kỹ thuật nhiều lần khi codebase của bạn tiến hóa. (Đoạn này khó dịch quá, đại khái ý nó là nên dùng interface thay vì dùng class)
  • Khi bạn xác định sự tương tác giữa các mô-đun, hãy suy nghĩ làm thế nào để làm cho mỗi một module có thể test được một cách độc lập. Ví dụ, việc có một API được định nghĩa rõ ràng để lấy dữ liệu từ mạng sẽ làm cho việc kiểm tra mô-đun vẫn tồn tại dữ liệu trong cơ sở dữ liệu cục bộ dễ dàng hơn. Nếu thay vào đó, bạn trộn logic từ hai mô-đun này ở một nơi, hoặc rải rác mã lấy dữ liệu từ mạng của bạn trên toàn bộ cơ sở mã của bạn, sẽ khó khăn hơn - nếu không thể - để kiểm tra.
  • Cốt lõi của ứng dụng là cái làm cho nó nổi bật so với phần còn lại. Đừng tốn thời gian của bạn để phát minh lại cái bánh xe hoặc viết lại cùng một mã lệnh. Thay vào đó, hãy tập trung năng lượng tinh thần của bạn vào những gì làm cho ứng dụng của bạn trở nên độc đáo, còn lại hãy để Android Architecture Components và các thư viện được đề xuất khác giải quyết các vấn đề kỹ thuật.
  • Lưu giữ càng nhiều dữ liệu liên quan và mới nhất để có thể sử dụng ứng dụng của bạn khi thiết bị đang ở chế độ ngoại tuyến. Mặc dù bạn có thể kết nối liên tục và tốc độ cao nhưng người dùng của bạn có thể không.
  • Repository của bạn nên chỉ định một nguồn dữ liệu làm nguồn tin cậy duy nhất. Bất cứ khi nào ứng dụng của bạn cần truy cập vào phần dữ liệu này, nó luôn phải xuất phát từ nguồn đó. Để biết thêm thông tin, xem Single source of truth.

(Còn tiếp) Nguồn: https://developer.android.com/topic/libraries/architecture/index.html

Như các bạn đã biết thì Android hiện tại đang chạy trên hàng tỷ thiết bị, từ điện thoại cao cấp, đồng hồ cho đến seatbacks trên máy bay. Tuy nhiên Google lại ko đưa ra bất cứ một chuẩn thiết kế nào dành cho developers. Các bạn có thể biết tới MVC, MVP, MVVM... và rất nhiều Architecture Pattern khác nhưng tuyệt nhiên chúng ko phải là một chuẩn thiết kế được google khuyến cáo sử dụng. Từ trước đến giờ thì Google không hề suggest bất cứ gì về Architecture Components cả. Tuy nhiên tại sự kiện Google I/O 2017 vừa qua Google đã tung ra một chuẩn về thiết kế ứng dụng: Architecture Components

  • Persist Data
  • Manager Lifecycle
  • Make app modular
  • Avoid memory leak
  • Less boilerplate code

Theo như Google công bố thì Architecture Components gồm có 4 thành phần

  1. Room
  2. LiveData
  3. LifeCycle
  4. ViewModel

1. Room

Room là một nó là một abstract layer cung cấp cách thức truy câp thao tác với dữ liệu trong cơ sở dữ liệu SQLite cực kì mạnh mẽ. Các bạn có thể theo dõi kĩ hơn cách sử dụng Room tại bài viết này của mình để hiểu rõ hơn về Room và cách sử dụng nó nhé.

Để có thể tạo table thông qua Room các bạn cần định nghĩa một Plain Old Java Object (POJO) và đánh dấu POJO này với anotation @Entity

Trail.java @Entity public class Trail { public @PrimaryKey String id; public String name; public double kilometers; public int difficulty; }

Với mỗi một POJO các bạn cần định nghĩa một Dao (Data access object)

TrailDao.java @Dao public interface TrailDao { @Insert(onConfict = IGNORE) void insertTrail(Trail trail); @Query("SELECT * FROM Trail") List findAllTrails(); @Update(onConflict = REPLACE) void updateTrail(Trail trail); @Query("DELETE FROM Trail") void deleteAll(); }

Anotation @Dao này đại diện cho câu lệnh SQLite sẽ tương tác với POJO mà các bạn đã định nghĩa ra ở trên. Như các bạn đã thấy ở trên Room đã tự động convert data trong SQLite và trả về dữ liệu dưới dạng POJO List Điều đặc biệt là Room verifies các câu lệnh SQLite của các bạn vào lúc compile chính vì vậy các bạn hoàn toàn có thể biết được mình viết câu lệnh có đúng hay không mà không cần phải chạy app và xem thử. Điều này SQLite Helper trước đó chưa thể thực hiện.

4 component chính trong Android là gì

Ok các bạn đã có Room database rồi các bạn có thể sử dụng một architecture mới của Android là LiveData để có thể theo dõi sự thay đổi của dữ liệu trong database một cách realtime.

2. LiveData

4 component chính trong Android là gì

LiveData là một kiểu dữ liệu có thể quan sát được, nó có thể thông báo ngay lập tức khi có sự thay đổi về data vì vậy mà các bạn có thể update lại giao diện ngay lập tức. Ngoài ra nó hoàn toàn có thể nhận biết được lifecycle (LifeCycler Aware - lát nói sau nha) LiveData là một abstract class vì vậy các bạn hoàn toàn có thể extend LiveData hoặc đơn giản các bạn có thể sử dụng MutableLiveData class

MutableLiveData dayOfWeek = new MutableLiveData<>(); dayOfWeek.observer(this, data -> { mTextView.setText(dayOfWeek.getValue() + "is a good day for a hike"); });

4 component chính trong Android là gì

Và ngay khi các bạn update value của LiveData thì UI của bạn sẽ được update

dayOfWeek.setValue("Friday");

4 component chính trong Android là gì

Nhưng tuyệt vời hơn nữa rằng Room được xây dựng để hỗ trợ cho LiveData. Để sử dụng Room kết hợp với LiveData các bạn chỉ cần update DAO

TrailDao.java @Dao public interface TrailDao { @Insert(onConfict = IGNORE) void insertTrail(Trail trail); // @Query("SELECT * FROM Trail") // List findAllTrails(); // Change List to LiveData> @Query("SELECT * FROM Trail") LiveData> findAllTrails(); @Update(onConflict = REPLACE) void updateTrail(Trail trail); @Query("DELETE FROM Trail") void deleteAll(); }

Room sẽ tạo ra một LiveData object để lắng nghe database, khi database có sự thay đổi LiveData sẽ thông báo và các bạn update ui

trailsLiveData.observe(this, trails - > { // Update UI, in this case a RecyclerView mTrailsRecyclerAdapter.replaceItems(trails); mTrailsRecyclerAdapter.notifyDataSetChanged(); });

Như mình định nghĩa ở trên thì LiveData là một lifecycle-aware component (Là component có thể nhận biết vòng đới) Đến đây thì chắc nhiều bạn sẽ tự hỏi lifecycle-aware component là gì? LifeCycle Aware Component

  • On Screen
  • Off Screen
  • Destroyed LiveData biết được khi nào activity đang on screen. off screen, hoặc destroy từ đó mà LiveData không gọi database update khi mà không có UI. Thật tối ưu phải không nào?

Có 2 interface phục vụ cho việc này là Lifecycle OwnersLifecycle Observers

3. Lifecycle

Lifecycle là gì thì mình sẽ không giải thích thêm nữa nhé

4 component chính trong Android là gì
mình chỉ giải thích Lifecycle trong LiveData thôi

Lifecycle trong LiveData gồm có Lifecycle Owners và LifecyclObservers

  • Lifecycle Owner là những object có lifecycles như Activities, fragments
  • LifecycleObservers lắng nghe Lifecycle Owner và thông báo khi lifecycle thay đổi.

Đây là một ví dụ đơn giản về LiveData cũng là ví dụ về Lifecycle Observer

abstract public class LiveData implements LifecycleObserver{ @OnLifecycleEvent(Lifecycle.Event.ON_START) void startup(){ } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) void cleanup(){ } }

Những menthod mà được định nghĩa với anotaion @OnLifecycleEvent sẽ lắng nghe sự thay đổi của các Lifecycle Owner khi nó được khởi tạo hoặc destroy ...

DESTROYED, INITIALIZED, CREATED, STARTED, RESUMED

Flow cụ thể như sau

4 component chính trong Android là gì

Các UI components lắng nghe LiveData, LiveData lắng nghe LifecycleOwners (Fragments/Activities)

Có một vấn đề mà chắc hẳn là android developer các bạn ai cũng đã từng mắc phải đó là xử lý khi mà người dùng "XOAY MÀN HÌNH"

4 component chính trong Android là gì
Ví dụ là khi màn hình bị xoay việc query data của các bạn lại diễn ra một lần nữa, thật là phiền phức phải không nào? Google đã nghe được lời đó của các con chiên developer, và ViewModel được ra đời.

4. ViewModel

4 component chính trong Android là gì
View models là một objects cung cấp data cho UI components và luôn được giữ nguyên ngay cả khi thay đổi các thiết lập (ví dụ như screen rotation) Để tạo ra một ViewModel các bạn cần extend AndroidViewModel và put tất cả những data mà các bạn muốn sử dụng cho activity vào class đó

public class TrailListViewModel extends AndroidViewModel { private AppDatabasse mDatabase; private LiveData> trails; public TrailListViewModel(Application application){ super(application); // AppDatabase is a Room database singleton mDatabase = AppDatabase.getDb(getApplication()); trails = mDatabase.trailModel().findAllTrails(); } }

Khi các bạn đặt data vào trong ViewModel thì ứng dụng sẽ ko phải khởi tạo lại data khi activity được khởi tạo lại sau khi configuration thay đổi.

Tổng kết

Thông thường thì các ứng dụng Android sẽ được xây dựng như sau

4 component chính trong Android là gì

Trên đây là phần trình bày của mình về Architecture Components Các bạn có thể tham khảo thêm về Architecture Components tại đây nhé Demo mà mình thực hiện tại đây

Cám ơn các bạn đã đón đọc và chúc các bạn học tốt!