Thiết kế để mang lại sự liền mạch

Ngay cả khi ứng dụng của bạn hoạt động nhanh và có khả năng thích ứng, một số quyết định thiết kế nhất định vẫn có thể gây ra sự cố cho người dùng — do tương tác ngoài dự kiến với các ứng dụng hoặc hộp thoại khác, vô tình mất dữ liệu, chặn ngoài ý muốn, v.v. Để tránh những vấn đề này, bạn nên hiểu ngữ cảnh trong đó ứng dụng chạy và những tương tác hệ thống có thể ảnh hưởng đến ứng dụng của bạn. Tóm lại, bạn nên cố gắng phát triển một ứng dụng tương tác liền mạch với hệ thống và với các ứng dụng khác.

Một vấn đề phổ biến về tính liền mạch là khi quy trình chạy trong nền của ứng dụng (ví dụ như dịch vụ hoặc broadcast receiver) bật lên hộp thoại để phản hồi một sự kiện nào đó. Điều này có vẻ như là một hành vi vô hại, đặc biệt là khi bạn đang xây dựng và kiểm thử ứng dụng một cách riêng biệt, trên trình mô phỏng. Tuy nhiên, khi ứng dụng của bạn chạy trên một thiết bị thực tế, ứng dụng có thể không tập trung vào người dùng tại thời điểm quy trình nền hiển thị hộp thoại. Vì vậy, có thể ứng dụng của bạn sẽ hiển thị hộp thoại phía sau ứng dụng đang hoạt động, hoặc có thể lấy tiêu điểm từ ứng dụng hiện tại và hiển thị hộp thoại ở phía trước bất kỳ thao tác nào người dùng đang thực hiện (chẳng hạn như gọi điện thoại). Hành vi đó sẽ không hiệu quả với ứng dụng của bạn hoặc người dùng.

Để tránh những vấn đề này, ứng dụng của bạn phải sử dụng cơ sở hệ thống thích hợp để thông báo cho người dùng — các lớp Notification. Khi sử dụng thông báo, ứng dụng của bạn có thể báo cho người dùng rằng một sự kiện đã xảy ra, bằng cách hiển thị một biểu tượng trên thanh trạng thái thay vì lấy tiêu điểm và làm gián đoạn người dùng.

Một ví dụ khác về vấn đề liền mạch là khi một hoạt động vô tình làm mất trạng thái hoặc dữ liệu người dùng do không triển khai đúng cách onPause() và các phương thức khác trong vòng đời. Hoặc, nếu ứng dụng của bạn hiển thị dữ liệu mà các ứng dụng khác dự định dùng, thì bạn nên hiển thị dữ liệu đó qua ContentProvider, thay vì (ví dụ) làm như vậy thông qua tệp thô hoặc cơ sở dữ liệu thô có thể đọc được trên toàn thế giới.

Điểm chung của những ví dụ đó là chúng liên quan đến việc hợp tác tốt với hệ thống và các ứng dụng khác. Hệ thống Android được thiết kế để coi ứng dụng là một kiểu liên kết gồm các thành phần được kết hợp lỏng lẻo, thay vì các đoạn mã hộp đen. Điều này cho phép nhà phát triển xem toàn bộ hệ thống dưới dạng một liên kết lớn hơn nữa gồm các thành phần này. Điều này mang lại lợi ích cho bạn khi cho phép tích hợp dễ dàng và liền mạch với các ứng dụng khác. Vì vậy, bạn nên thiết kế mã của riêng mình để đáp lại sự ưu ái này.

Tài liệu này thảo luận các vấn đề thường gặp về tính liền mạch và cách tránh các vấn đề đó.

Không thả dữ liệu

Luôn nhớ rằng Android là một nền tảng di động. Điều này có vẻ hiển nhiên, nhưng điều quan trọng cần nhớ là một Hoạt động khác (chẳng hạn như ứng dụng "Cuộc gọi điện thoại đến") có thể bật lên Hoạt động của bạn bất cứ lúc nào. Việc này sẽ kích hoạt các phương thức onSaveInstanceState() và onPause() và có thể khiến ứng dụng của bạn bị tắt.

Nếu người dùng đang chỉnh sửa dữ liệu trong ứng dụng khi Hoạt động khác xuất hiện, thì ứng dụng của bạn có thể sẽ mất dữ liệu đó khi ứng dụng bị tắt. Tất nhiên, trừ phi bạn lưu công việc đang tiến hành trước. "Cách thức Android" là để thực hiện điều đó: Các ứng dụng Android chấp nhận hoặc chỉnh sửa thông tin đầu vào sẽ ghi đè phương thức onSaveInstanceState() và lưu trạng thái của chúng ở một số cách thích hợp. Khi người dùng truy cập lại ứng dụng, cô ấy sẽ có thể truy xuất dữ liệu của mình.

Ví dụ điển hình về việc sử dụng tốt hành vi này là ứng dụng thư. Nếu người dùng đang soạn email khi một Hoạt động khác khởi động, thì ứng dụng sẽ lưu email trong quá trình dưới dạng thư nháp.

Không hiển thị dữ liệu thô

Nếu không mặc đồ lót xuống phố, bạn cũng không nên dùng dữ liệu của mình. Mặc dù có thể hiển thị một số loại ứng dụng nhất định cho mọi người đọc, nhưng đây thường không phải là ý tưởng tốt nhất. Việc hiển thị dữ liệu thô đòi hỏi các ứng dụng khác phải hiểu định dạng dữ liệu của bạn; nếu thay đổi định dạng đó, bạn sẽ làm hỏng mọi ứng dụng khác không được cập nhật tương tự.

"Cách thức Android" là tạo ContentProvider để hiển thị dữ liệu của bạn cho các ứng dụng khác thông qua một API sạch, được cân nhắc kỹ lưỡng và dễ bảo trì. Việc sử dụng ContentProvider cũng giống như cách chèn giao diện ngôn ngữ Java để phân tách và thành phần hoá 2 đoạn mã được kết hợp chặt chẽ. Điều này có nghĩa là bạn có thể sửa đổi định dạng nội bộ của dữ liệu mà không cần thay đổi giao diện do ContentProvider hiển thị và điều này mà không ảnh hưởng đến các ứng dụng khác.

Không làm gián đoạn người dùng

Nếu người dùng đang chạy một ứng dụng (chẳng hạn như ứng dụng Điện thoại trong khi gọi điện), thì chắc chắn là người dùng đó đã cố ý làm việc đó. Đó là lý do tại sao bạn nên tránh tạo các hoạt động ngoại trừ phản hồi trực tiếp cho hoạt động đầu vào của người dùng từ Hoạt động hiện tại.

Nghĩa là không gọi startActivity() từ BroadcastReceivers hoặc Dịch vụ đang chạy ở chế độ nền. Việc này sẽ làm gián đoạn bất kỳ ứng dụng nào đang chạy và khiến người dùng khó chịu. Thậm chí có thể tệ hơn nữa, Hoạt động của bạn có thể trở thành "nhóm thử nghiệm thao tác nhấn phím" và nhận một số dữ liệu đầu vào mà người dùng đang cung cấp cho Hoạt động trước đó. Tuỳ thuộc vào chức năng của ứng dụng, đây có thể là tin xấu.

Thay vì tạo giao diện người dùng Activity (Hoạt động) trực tiếp từ nền, bạn nên sử dụng NotificationManager để đặt Thông báo. Các nút này sẽ xuất hiện trên thanh trạng thái và sau đó người dùng có thể nhấp vào chúng khi tuỳ ý để xem ứng dụng của bạn phải hiển thị nội dung gì.

(Lưu ý rằng tất cả điều này không áp dụng cho các trường hợp mà Hoạt động của bạn đã ở nền trước: trong trường hợp đó, người dùng muốn thấy Hoạt động tiếp theo của bạn để phản hồi hoạt động đầu vào.)

Bạn có nhiều việc cần làm? Thực hiện trong một chuỗi

Nếu ứng dụng của bạn cần thực hiện một số phép tính tốn kém hoặc chạy trong thời gian dài, thì có thể bạn nên chuyển ứng dụng đó sang một luồng. Việc này sẽ ngăn hộp thoại "Ứng dụng không phản hồi" đáng sợ hiển thị với người dùng, với kết quả sau cùng là sự sụp đổ của ứng dụng.

Theo mặc định, tất cả mã trong một Hoạt động cũng như tất cả các Khung hiển thị của hoạt động đó đều chạy trong cùng một luồng. Đây cũng chính là luồng xử lý các sự kiện trên giao diện người dùng. Ví dụ: khi người dùng nhấn một phím, một sự kiện nhấn phím sẽ được thêm vào hàng đợi của luồng chính của Hoạt động. Hệ thống trình xử lý sự kiện cần nhanh chóng gỡ bỏ hàng đợi và xử lý sự kiện đó; nếu không, hệ thống sẽ kết thúc sau vài giây rằng ứng dụng bị treo và đề xuất loại bỏ ứng dụng cho người dùng.

Nếu bạn có mã chạy trong thời gian dài, việc chạy mã cùng dòng trong Hoạt động sẽ chạy mã đó trên luồng của trình xử lý sự kiện, giúp chặn trình xử lý sự kiện một cách hiệu quả. Việc này sẽ làm chậm trễ quá trình xử lý dữ liệu đầu vào và dẫn đến hộp thoại lỗi ANR xuất hiện. Để tránh điều này, hãy di chuyển các phép tính sang một luồng. Tài liệu Thiết kế để đáp ứng này sẽ thảo luận về cách thực hiện điều đó.

Không làm quá tải một màn hình hoạt động

Bất kỳ ứng dụng nào đáng sử dụng đều có thể có một vài màn hình. Khi thiết kế các màn hình giao diện người dùng, hãy nhớ sử dụng nhiều thực thể đối tượng Hoạt động.

Tuỳ thuộc vào nền tảng phát triển, bạn có thể diễn giải một Hoạt động tương tự như một Chương trình phụ trợ Java, vì đó là điểm truy cập cho ứng dụng. Tuy nhiên, điều đó không chính xác hoàn toàn: trong đó một lớp con Applet là điểm truy cập duy nhất cho một Applet Java, thì một Hoạt động nên được coi là một trong nhiều điểm truy cập có thể đến ứng dụng của bạn. Điểm khác biệt duy nhất giữa Hoạt động "chính" của bạn và các Hoạt động khác mà bạn có thể có là hoạt động "chính" chỉ là hoạt động duy nhất thể hiện sự quan tâm đến hành động "android.intent.action.MAIN" trong tệp AndroidManifest.xml.

Vì vậy, khi thiết kế ứng dụng, hãy xem ứng dụng là một liên kết các đối tượng Hoạt động. Điều này sẽ giúp mã của bạn dễ bảo trì hơn về lâu dài, đồng thời hiệu ứng phụ thú vị cũng hoạt động tốt với nhật ký ứng dụng và mô hình "ngăn xếp lui" của Android.

Mở rộng giao diện của hệ thống

Khi nói đến giao diện người dùng, điều quan trọng là phải kết hợp hài hoà. Người dùng khó chịu trước những ứng dụng trái ngược với giao diện người dùng mà họ kỳ vọng. Khi thiết kế giao diện người dùng, bạn nên cố gắng và tránh triển khai giao diện người dùng của riêng mình nhiều nhất có thể. Thay vào đó, hãy dùng Giao diện. Bạn có thể ghi đè hoặc mở rộng các phần đó của giao diện mà mình cần, nhưng ít nhất bạn đang bắt đầu từ cùng một cơ sở giao diện người dùng như tất cả ứng dụng khác. Để biết tất cả thông tin chi tiết, hãy đọc bài viết Kiểu và giao diện.

Thiết kế giao diện người dùng để hoạt động với nhiều độ phân giải màn hình

Các thiết bị chạy Android khác nhau sẽ hỗ trợ độ phân giải màn hình khác nhau. Một số trình xử lý thậm chí có thể thay đổi độ phân giải một cách nhanh chóng, chẳng hạn như bằng cách chuyển sang chế độ ngang. Điều quan trọng là bạn phải đảm bảo bố cục và đối tượng có thể vẽ của bạn đủ linh hoạt để hiển thị đúng cách trên nhiều màn hình thiết bị.

May mắn là việc này rất dễ thực hiện. Tóm lại, bạn cần cung cấp nhiều phiên bản hình minh hoạ (nếu sử dụng) cho độ phân giải phím, sau đó thiết kế bố cục phù hợp với nhiều kích thước. (Ví dụ: tránh sử dụng các vị trí được cố định giá trị trong mã và thay vào đó, hãy dùng bố cục tương đối.) Nếu bạn làm nhiều như vậy, hệ thống sẽ xử lý phần còn lại và ứng dụng của bạn sẽ trông đẹp mắt trên mọi thiết bị.

Giả sử mạng chậm

Các thiết bị Android sẽ có nhiều tuỳ chọn kết nối mạng. Tất cả ứng dụng đều sẽ có một số cấp truy cập dữ liệu, mặc dù một số ứng dụng sẽ nhanh hơn các quyền truy cập khác. Tuy nhiên, mẫu số chung nhỏ nhất là GPRS, dịch vụ dữ liệu không phải 3G cho các mạng GSM. Ngay cả các thiết bị hỗ trợ 3G cũng sẽ dành nhiều thời gian trên các mạng không phải 3G, vì vậy, mạng chậm sẽ vẫn còn là thực tế trong một thời gian dài sau đó.

Đó là lý do bạn phải luôn lập trình cho ứng dụng để giảm thiểu quyền truy cập mạng và băng thông. Bạn không thể giả định rằng mạng nhanh, vì vậy bạn phải luôn lên kế hoạch cho mạng chậm. Nếu người dùng của bạn sử dụng mạng nhanh hơn thì thật tuyệt vời — trải nghiệm của họ sẽ chỉ được cải thiện. Tuy nhiên, bạn muốn tránh trường hợp ngược: các ứng dụng có thể sử dụng được trong một số thời điểm nhưng làm chậm các ứng dụng còn lại một cách đáng thất vọng dựa trên vị trí của người dùng tại một thời điểm nhất định bất kỳ có khả năng sẽ không phổ biến.

Nếu đang sử dụng trình mô phỏng, thì bạn có thể dễ dàng mắc bẫy này, vì trình mô phỏng sử dụng kết nối mạng của máy tính. Điều đó gần như đảm bảo sẽ nhanh hơn nhiều so với mạng di động, vì vậy bạn cần thay đổi các chế độ cài đặt trên trình mô phỏng để mô phỏng tốc độ mạng chậm hơn. Bạn có thể thực hiện việc này trong Android Studio thông qua Trình quản lý thiết bị ảo Android hoặc thông qua tuỳ chọn dòng lệnh khi khởi động trình mô phỏng.

Không giả định màn hình cảm ứng hoặc bàn phím

Android sẽ hỗ trợ nhiều kiểu dáng điện thoại di động. Có một cách thú vị để nói rằng một số thiết bị Android sẽ có bàn phím "QWERTY" đầy đủ, trong khi các thiết bị khác sẽ có bàn phím 40 phím, 12 phím hoặc thậm chí là các cấu hình phím khác. Tương tự, một số thiết bị sẽ có màn hình cảm ứng, nhưng nhiều thiết bị thì không.

Khi phát triển các ứng dụng, hãy ghi nhớ điều đó. Đừng đưa ra giả định về bố cục bàn phím cụ thể -- dĩ nhiên trừ phi bạn thực sự quan tâm đến việc hạn chế ứng dụng để ứng dụng chỉ có thể được sử dụng trên các thiết bị đó.

Tiết kiệm pin của thiết bị

Một thiết bị di động không hẳn là di động nếu liên tục được cắm vào tường. Thiết bị di động chạy bằng pin và chúng tôi có thể kéo dài thời lượng pin của thiết bị khi sạc càng lâu thì mọi người càng hài lòng hơn — đặc biệt là người dùng. Hai trong những đối tượng tiêu thụ pin nhiều nhất là bộ xử lý và đài. Đó là lý do bạn phải viết ứng dụng để ít phải làm việc nhất có thể, đồng thời sử dụng mạng ít thường xuyên nhất có thể.

Việc giảm thiểu thời gian của bộ xử lý ứng dụng thực sự giúp giảm thiểu tác động của việc viết mã hiệu quả. Để giảm thiểu tình trạng tiêu hao pin khi sử dụng đài, hãy đảm bảo xử lý các điều kiện lỗi một cách linh hoạt và chỉ tìm nạp những dữ liệu bạn cần. Ví dụ: đừng liên tục thử lại một thao tác mạng nếu một thao tác mạng không thành công. Nếu lỗi một lần, nguyên nhân có thể là do người dùng không có kết nối Internet, vì vậy, nếu bạn thử lại ngay, tất cả những gì bạn sẽ làm là lãng phí pin.

Người dùng khá thông minh: nếu chương trình của bạn ngốn pin, bạn có thể tin tưởng vào sự chú ý của chúng. Điều duy nhất bạn có thể chắc chắn tại thời điểm đó là chương trình của bạn sẽ không phải cài đặt quá lâu.