Tôi đã viết câu trả lời này vào năm '09 khi Android còn khá mới và có nhiều khu vực chưa được thiết lập tốt trong phát triển Android. Tôi đã thêm một phụ lục dài ở cuối bài này, giải quyết một số lời chỉ trích và nêu chi tiết về sự bất đồng về triết học mà tôi có với việc sử dụng Singletons thay vì phân loại Ứng dụng. Đọc nó có nguy cơ của riêng bạn.
TRẢ LỜI GỐC:
Vấn đề chung hơn mà bạn đang gặp phải là làm thế nào để lưu trạng thái qua một số Hoạt động và tất cả các phần trong ứng dụng của bạn. Một biến tĩnh (ví dụ, một singleton) là một cách phổ biến của Java để đạt được điều này. Tuy nhiên, tôi đã thấy rằng một cách thanh lịch hơn trong Android là liên kết trạng thái của bạn với bối cảnh Ứng dụng.
Như bạn đã biết, mỗi Hoạt động cũng là một Ngữ cảnh, là thông tin về môi trường thực thi của nó theo nghĩa rộng nhất. Ứng dụng của bạn cũng có ngữ cảnh và Android đảm bảo rằng nó sẽ tồn tại dưới dạng một thể hiện duy nhất trên ứng dụng của bạn.
Cách để làm điều này là tạo lớp con android.app.Application của riêng bạn , sau đó chỉ định lớp đó trong thẻ ứng dụng trong tệp kê khai của bạn. Bây giờ Android sẽ tự động tạo một thể hiện của lớp đó và làm cho nó có sẵn cho toàn bộ ứng dụng của bạn. Bạn có thể truy cập nó từ bất kỳ context
bằng cách sử dụng Context.getApplicationContext()
phương thức ( Activity
cũng cung cấp một phương thức getApplication()
có hiệu quả chính xác tương tự). Sau đây là một ví dụ cực kỳ đơn giản, hãy cẩn thận:
class MyApp extends Application {
private String myState;
public String getState(){
return myState;
}
public void setState(String s){
myState = s;
}
}
class Blah extends Activity {
@Override
public void onCreate(Bundle b){
...
MyApp appState = ((MyApp)getApplicationContext());
String state = appState.getState();
...
}
}
Điều này về cơ bản có tác dụng tương tự như sử dụng biến tĩnh hoặc singleton, nhưng tích hợp khá tốt vào khung Android hiện có. Lưu ý rằng điều này sẽ không hoạt động trên các quy trình (nếu ứng dụng của bạn là một trong những quy trình hiếm hoi có nhiều quy trình).
Một vài điều cần lưu ý từ ví dụ trên; giả sử thay vào đó chúng ta đã làm một cái gì đó như:
class MyApp extends Application {
private String myState = /* complicated and slow initialization */;
public String getState(){
return myState;
}
}
Bây giờ việc khởi tạo chậm này (chẳng hạn như nhấn đĩa, nhấn mạng, chặn mọi thứ, v.v.) sẽ được thực hiện mỗi khi Ứng dụng được khởi tạo! Bạn có thể nghĩ rằng, đây chỉ là một lần cho quá trình và tôi sẽ phải trả chi phí dù sao, phải không? Ví dụ, như Dianne Hackborn đề cập dưới đây, hoàn toàn có thể quy trình của bạn được khởi tạo - điều chỉnh - để xử lý một sự kiện phát sóng nền. Nếu quá trình xử lý phát sóng của bạn không cần trạng thái này, bạn có khả năng chỉ cần thực hiện một loạt các hoạt động phức tạp và chậm chạp mà không có gì. Lười ngay lập tức là tên của trò chơi ở đây. Sau đây là cách sử dụng Ứng dụng phức tạp hơn một chút, điều này có ý nghĩa hơn đối với mọi thứ trừ cách sử dụng đơn giản nhất:
class MyApp extends Application {
private MyStateManager myStateManager = new MyStateManager();
public MyStateManager getStateManager(){
return myStateManager ;
}
}
class MyStateManager {
MyStateManager() {
/* this should be fast */
}
String getState() {
/* if necessary, perform blocking calls here */
/* make sure to deal with any multithreading/synchronicity issues */
...
return state;
}
}
class Blah extends Activity {
@Override
public void onCreate(Bundle b){
...
MyStateManager stateManager = ((MyApp)getApplicationContext()).getStateManager();
String state = stateManager.getState();
...
}
}
Mặc dù tôi thích phân lớp ứng dụng hơn là sử dụng singletons ở đây như một giải pháp thanh lịch hơn, nhưng tôi muốn các nhà phát triển sử dụng singletons nếu thực sự cần thiết hơn là không suy nghĩ gì cả thông qua hiệu năng và đa luồng của trạng thái liên kết với lớp con Ứng dụng.
LƯU Ý 1: Cũng như nhận xét về an toàn, để buộc chính xác ghi đè Ứng dụng của bạn vào ứng dụng của bạn, một thẻ là cần thiết trong tệp kê khai. Một lần nữa, xem tài liệu Android để biết thêm. Một ví dụ:
<application
android:name="my.application.MyApp"
android:icon="..."
android:label="...">
</application>
CHÚ THÍCH 2: user608578 hỏi bên dưới cách thức này hoạt động với việc quản lý vòng đời đối tượng gốc. Tôi không theo kịp tốc độ sử dụng mã gốc với Android và tôi không đủ điều kiện để trả lời cách thức tương tác với giải pháp của mình. Nếu ai đó có câu trả lời cho điều này, tôi sẵn sàng ghi có cho họ và đưa thông tin vào bài đăng này để hiển thị tối đa.
ĐỊA CHỈ:
Như một số người đã lưu ý, đây không phải là một giải pháp cho trạng thái dai dẳng , có lẽ tôi nên nhấn mạnh nhiều hơn trong câu trả lời ban đầu. Tức là điều này không có nghĩa là một giải pháp để lưu người dùng hoặc thông tin khác có nghĩa là được duy trì trong suốt vòng đời của ứng dụng. Vì vậy, tôi cho rằng hầu hết những lời chỉ trích dưới đây liên quan đến Ứng dụng bị giết bất cứ lúc nào, v.v ..., vì vậy, mọi thứ cần được duy trì trên đĩa không nên được lưu trữ thông qua một lớp con Ứng dụng. Nó có nghĩa là một giải pháp để lưu trữ trạng thái ứng dụng tạm thời, có thể tạo lại dễ dàng (ví dụ người dùng đã đăng nhập) và các thành phần là một trường hợp duy nhất (ví dụ như trình quản lý mạng ứng dụng) ( KHÔNG phải là singleton!).
Dayerman đã rất tử tế khi chỉ ra một cuộc trò chuyện thú vị với Reto Meier và Dianne Hackborn, trong đó việc sử dụng các lớp con Ứng dụng không được khuyến khích trong các mẫu Singleton. Somatik cũng đã chỉ ra một cái gì đó thuộc về bản chất này sớm hơn, mặc dù lúc đó tôi không thấy nó. Do vai trò của Reto và Dianne trong việc duy trì nền tảng Android, tôi không thể thực sự khuyên bạn nên bỏ qua lời khuyên của họ. Những gì họ nói, đi. Tôi muốn không đồng ý với các ý kiến, bày tỏ liên quan đến việc thích Singleton hơn các lớp con Ứng dụng. Trong sự bất đồng của tôi, tôi sẽ sử dụng các khái niệm được giải thích tốt nhất trong phần giải thích StackExchange này về mẫu thiết kế Singleton, để tôi không phải xác định thuật ngữ trong câu trả lời này. Tôi rất khuyến khích lướt qua các liên kết trước khi tiếp tục. Từng điểm:
Dianne tuyên bố: "Không có lý do gì để phân lớp từ Ứng dụng. Không khác gì làm một đơn ..." Yêu cầu đầu tiên này không chính xác. Có hai lý do chính cho việc này. 1) Lớp Ứng dụng cung cấp bảo đảm trọn đời tốt hơn cho nhà phát triển ứng dụng; nó được đảm bảo có tuổi thọ của ứng dụng. Một singleton không bị ràng buộc chặt chẽ với thời gian tồn tại của ứng dụng (mặc dù nó có hiệu quả). Đây có thể không phải là vấn đề đối với nhà phát triển ứng dụng trung bình của bạn, nhưng tôi cho rằng đây chính xác là loại hợp đồng mà API Android sẽ cung cấp và nó cũng cung cấp sự linh hoạt hơn cho hệ thống Android, bằng cách giảm thiểu thời gian tồn tại của liên kết dữ liệu. 2) Lớp Ứng dụng cung cấp cho nhà phát triển ứng dụng một chủ sở hữu cá thể duy nhất cho trạng thái, đó là rất khác nhau từ một chủ sở hữu nhà nước Singleton. Để biết danh sách về sự khác biệt, xem liên kết giải thích Singleton ở trên.
Dianne tiếp tục, "... rất có thể sẽ là điều bạn hối tiếc trong tương lai khi bạn thấy đối tượng Ứng dụng của mình trở thành mớ hỗn độn lớn về những gì nên là logic ứng dụng độc lập." Điều này chắc chắn không phải là không chính xác, nhưng đây không phải là lý do để chọn Singleton trên lớp con Ứng dụng. Không có lý lẽ nào của Diane đưa ra lý do rằng sử dụng Singleton tốt hơn lớp con Ứng dụng, tất cả những gì cô ấy cố gắng thiết lập là việc sử dụng Singleton không tệ hơn lớp con Ứng dụng, điều mà tôi tin là sai.
Cô ấy tiếp tục, "Và điều này dẫn đến một cách tự nhiên hơn về cách bạn nên quản lý những thứ này - khởi tạo chúng theo yêu cầu." Điều này bỏ qua thực tế là không có lý do gì bạn không thể khởi tạo theo yêu cầu bằng cách sử dụng một lớp con Ứng dụng. Một lần nữa không có sự khác biệt.
Dianne kết thúc với "Bản thân khung này có hàng tấn tấn đơn cho tất cả các dữ liệu được chia sẻ nhỏ mà nó duy trì cho ứng dụng, chẳng hạn như bộ nhớ tài nguyên được tải, nhóm đối tượng, v.v. Nó hoạt động rất tốt." Tôi không tranh luận rằng sử dụng Singletons không thể hoạt động tốt hoặc không phải là một sự thay thế hợp pháp. Tôi cho rằng Singletons không cung cấp hợp đồng mạnh mẽ với hệ thống Android như sử dụng một lớp con Ứng dụng và hơn nữa việc sử dụng Singletons thường chỉ ra thiết kế không linh hoạt, không dễ sửa đổi và dẫn đến nhiều vấn đề. IMHO, hợp đồng mạnh mẽ mà API Android cung cấp cho các ứng dụng dành cho nhà phát triển là một trong những khía cạnh hấp dẫn và dễ chịu nhất của lập trình với Android, và đã giúp dẫn đến việc nhà phát triển sớm áp dụng nền tảng Android để đạt được thành công như ngày nay.
Dianne cũng đã bình luận bên dưới, đề cập đến một nhược điểm bổ sung cho việc sử dụng các lớp con Ứng dụng, họ có thể khuyến khích hoặc làm cho việc viết mã hiệu suất ít dễ dàng hơn. Điều này rất đúng và tôi đã chỉnh sửa câu trả lời này để nhấn mạnh tầm quan trọng của việc xem xét sự hoàn hảo ở đây và thực hiện phương pháp đúng nếu bạn đang sử dụng phân lớp Ứng dụng. Như Dianne tuyên bố, điều quan trọng cần nhớ là lớp Ứng dụng của bạn sẽ được khởi tạo mỗi khi quá trình của bạn được tải (có thể nhiều lần cùng một lúc nếu ứng dụng của bạn chạy trong nhiều quy trình!) Ngay cả khi quy trình chỉ được tải cho phát sóng nền biến cố. Do đó, điều quan trọng là sử dụng lớp Ứng dụng nhiều hơn như một kho lưu trữ con trỏ tới các thành phần được chia sẻ của ứng dụng của bạn chứ không phải là nơi để thực hiện bất kỳ xử lý nào!
Tôi để lại cho bạn danh sách các nhược điểm sau đối với Singletons, vì bị đánh cắp từ liên kết StackExchange trước đó:
- Không có khả năng sử dụng các lớp trừu tượng hoặc giao diện;
- Không có khả năng phân lớp;
- Khớp nối cao trên ứng dụng (khó sửa đổi);
- Khó kiểm tra (không thể giả / giả trong các bài kiểm tra đơn vị);
- Khó song song trong trường hợp trạng thái đột biến (yêu cầu khóa mở rộng);
và thêm của riêng tôi:
- Hợp đồng trọn đời không rõ ràng và không thể quản lý không được sử dụng cho phát triển Android (hoặc hầu hết khác);