Singletons vs Bối cảnh ứng dụng trong Android?


362

Nhắc lại bài đăng này liệt kê một số vấn đề khi sử dụng singletons và đã thấy một số ví dụ về các ứng dụng Android sử dụng mẫu singleton, tôi tự hỏi liệu có nên sử dụng Singletons thay vì các trường hợp duy nhất được chia sẻ thông qua trạng thái ứng dụng toàn cầu (phân lớp android.os.Ứng dụng và lấy nó thông qua bối cảnh.getApplication ()).

Những cơ chế / nhược điểm nào cả hai cơ chế sẽ có?

Thành thật mà nói, tôi mong đợi câu trả lời tương tự trong bài đăng này mẫu Singleton với ứng dụng Web, Không phải là một ý tưởng tốt! nhưng áp dụng cho Android. Tôi có đúng không? Có gì khác trong DalvikVM?

EDIT: Tôi muốn có ý kiến ​​về một số khía cạnh liên quan:

  • Đồng bộ hóa
  • Tái sử dụng
  • Kiểm tra

Câu trả lời:


295

Tôi rất không đồng ý với phản ứng của Dianne Hackborn. Chúng tôi từng chút một loại bỏ tất cả các singletons khỏi dự án của chúng tôi để ủng hộ các đối tượng phạm vi nhiệm vụ nhẹ, có thể dễ dàng được tạo lại khi bạn thực sự cần chúng.

Singletons là một cơn ác mộng để thử nghiệm và, nếu được khởi tạo một cách lười biếng, sẽ đưa ra "chủ nghĩa không xác định trạng thái" với các tác dụng phụ tinh tế (có thể đột nhiên xuất hiện khi chuyển các cuộc gọi getInstance()từ phạm vi này sang phạm vi khác). Khả năng hiển thị đã được đề cập như một vấn đề khác và vì singletons ngụ ý truy cập "toàn cầu" (= ngẫu nhiên) vào trạng thái chia sẻ, các lỗi tinh vi có thể phát sinh khi không được đồng bộ hóa chính xác trong các ứng dụng đồng thời.

Tôi coi đó là một kiểu chống đối, đó là một phong cách hướng đối tượng xấu, chủ yếu là để duy trì trạng thái toàn cầu.

Để trở lại câu hỏi của bạn:

Mặc dù bối cảnh ứng dụng có thể được coi là một singleton, nhưng nó được quản lý theo khung và có vòng đời , phạm vi và đường dẫn truy cập được xác định rõ . Do đó tôi tin rằng nếu bạn cần quản lý trạng thái toàn cầu ứng dụng, thì nó sẽ xuất hiện ở đây, không ở đâu khác. Đối với bất cứ điều gì khác, hãy suy nghĩ lại nếu bạn thực sự cần một đối tượng singleton hoặc nếu có thể cũng có thể viết lại lớp singleton của bạn để thay vào đó khởi tạo các đối tượng nhỏ, có thời gian thực hiện nhiệm vụ trong tầm tay.


131
Nếu bạn đang giới thiệu Ứng dụng, bạn nên sử dụng singletons. Thành thật mà nói, không có cách nào xung quanh nó. Ứng dụng một singleton, với ngữ nghĩa crappier. Tôi sẽ không tranh luận về tôn giáo về những điều đơn lẻ mà bạn không bao giờ nên sử dụng. Tôi thích thực tế hơn - có những nơi chúng là một lựa chọn tốt để duy trì trạng thái theo quy trình và có thể đơn giản hóa mọi thứ bằng cách làm như vậy, và bạn cũng có thể sử dụng chúng trong tình huống sai và tự bắn vào chân mình.
hackbod

18
Đúng, và tôi đã đề cập rằng "bối cảnh ứng dụng có thể được coi là một singleton". Sự khác biệt là với ví dụ ứng dụng, việc tự bắn vào chân mình khó hơn rất nhiều, vì vòng đời của nó được xử lý bởi khung. Các khung DI như Guice, Hivemind hoặc Spring cũng sử dụng các singletons, nhưng đó là một chi tiết triển khai mà nhà phát triển không nên quan tâm. Tôi nghĩ nói chung sẽ an toàn hơn khi dựa vào ngữ nghĩa khung được triển khai chính xác thay vì mã của riêng bạn. Vâng, tôi thừa nhận tôi làm! :-)
Matthias

93
Thành thật mà nói, điều đó không ngăn cản bạn tự bắn vào chân mình nhiều hơn một người độc thân. Nó hơi khó hiểu, nhưng không có vòng đời của Ứng dụng. Nó được tạo khi ứng dụng của bạn khởi động (trước khi bất kỳ thành phần nào của nó được khởi tạo) và onCreate () được gọi tại thời điểm đó và ... đó là tất cả. Nó ngồi đó và sống mãi mãi, cho đến khi quá trình bị giết. Giống như một người độc thân. :)
hackbod

30
Ồ, có một điều có thể gây nhầm lẫn này - Android được thiết kế rất nhiều xung quanh việc chạy các ứng dụng trong các quy trình và quản lý vòng đời của các quy trình đó. Vì vậy, trên Android singletons là một cách rất tự nhiên để tận dụng việc quản lý quy trình đó - nếu bạn muốn lưu trữ một cái gì đó trong quy trình của mình cho đến khi nền tảng cần lấy lại bộ nhớ của quy trình cho một thứ khác, đặt trạng thái đó vào một đơn vị sẽ làm điều đó.
hackbod

7
Được rồi đủ công bằng. Tôi chỉ có thể nói rằng tôi đã không nhìn lại kể từ khi chúng tôi rời xa những người độc thân tự quản. Bây giờ chúng tôi đang chọn một giải pháp kiểu DI nhẹ, trong đó chúng tôi giữ một đơn vị nhà máy (RootFactory), do đó được quản lý bởi ứng dụng (nếu đó là đại biểu nếu bạn muốn). Đơn vị này quản lý các phụ thuộc chung mà tất cả các thành phần ứng dụng dựa vào, nhưng tính năng khởi tạo được quản lý ở một vị trí duy nhất - lớp ứng dụng. Trong khi với cách tiếp cận đó, một singleton vẫn còn, nó bị giới hạn trong lớp Ứng dụng, vì vậy không có mô-đun mã nào khác biết về "chi tiết" đó.
Matthias

231

Tôi rất khuyên bạn nên singletons. Nếu bạn có một singleton cần một bối cảnh, có:

MySingleton.getInstance(Context c) {
    //
    // ... needing to create ...
    sInstance = new MySingleton(c.getApplicationContext());
}

Tôi thích singletons hơn Ứng dụng vì nó giúp giữ cho ứng dụng có tổ chức và mô đun hơn nhiều - thay vì có một nơi mà tất cả trạng thái toàn cầu của bạn trên ứng dụng cần được duy trì, mỗi phần riêng biệt có thể tự xử lý. Ngoài ra, thực tế là các singletons khởi tạo một cách lười biếng (theo yêu cầu) thay vì dẫn bạn xuống con đường thực hiện tất cả các khởi tạo trước trong Application.onCreate () là tốt.

Về bản chất không có gì sai khi sử dụng singletons. Chỉ cần sử dụng chúng một cách chính xác, khi nó có ý nghĩa. Khung công tác Android thực sự có rất nhiều trong số đó, để nó duy trì lưu trữ trên mỗi quy trình của các tài nguyên được tải và những thứ khác.

Ngoài ra, đối với các ứng dụng đơn giản, đa luồng không trở thành vấn đề với singletons, bởi vì theo thiết kế, tất cả các cuộc gọi lại tiêu chuẩn cho ứng dụng được gửi trên luồng chính của quy trình, do đó bạn sẽ không xảy ra đa luồng trừ khi bạn giới thiệu rõ ràng qua các luồng hoặc ngầm bằng cách xuất bản một nhà cung cấp nội dung hoặc dịch vụ IBinder cho các quy trình khác.

Chỉ cần suy nghĩ về những gì bạn đang làm. :)


1
Nếu một thời gian sau tôi muốn nghe một sự kiện bên ngoài hoặc chia sẻ trên IBinder (tôi đoán đó không phải là một ứng dụng đơn giản) tôi sẽ phải thêm khóa kép, đồng bộ hóa, dễ bay hơi, phải không? Cảm ơn câu trả lời của bạn :)
mschonaker

2
Không dành cho một sự kiện bên ngoài - BroadcastReceiver.onReceive () cũng được gọi trên luồng chính.
hackbod

2
Được chứ. Bạn có thể chỉ cho tôi một số tài liệu đọc (tôi thích mã hơn) nơi tôi có thể thấy cơ chế gửi luồng chính? Tôi nghĩ rằng sẽ làm rõ một số khái niệm cùng một lúc cho tôi. Cảm ơn trước.
mschonaker

2
Đây là mã điều phối phía ứng dụng chính: android.git.kernel.org/?p=pl
platform / frameworks / Kẻ

8
Về bản chất không có gì sai khi sử dụng singletons. Chỉ cần sử dụng chúng một cách chính xác, khi nó có ý nghĩa. .. chắc chắn, chính xác, cũng nói. Khung công tác Android thực sự có rất nhiều trong số đó, để nó duy trì lưu trữ trên mỗi quy trình của các tài nguyên được tải và những thứ khác. Chính xác như bạn nói. Từ bạn bè của bạn trong thế giới iOS, "mọi thứ đều là đơn lẻ" trong iOS .. không có gì có thể tự nhiên hơn trên các thiết bị vật lý ngoài khái niệm đơn lẻ: gps, đồng hồ, con quay, v.v. - về mặt khái niệm, bạn sẽ thiết kế những thứ đó như thế nào là người độc thân? Vì vậy, vâng.
Fattie

22

Từ: Nhà phát triển> tài liệu tham khảo - Ứng dụng

Thông thường không cần phải phân lớp Ứng dụng. Trong hầu hết các tình huống, singletons tĩnh có thể cung cấp chức năng tương tự theo cách mô đun hơn. Nếu singleton của bạn cần một bối cảnh toàn cầu (ví dụ để đăng ký máy thu quảng bá), chức năng truy xuất nó có thể được cung cấp một Ngữ cảnh sử dụng Context.getApplicationContext () khi lần đầu tiên xây dựng singleton.


1
Và nếu bạn viết một giao diện cho singleton, không để getInstance không tĩnh, bạn thậm chí có thể tạo hàm tạo mặc định của lớp sử dụng singleton tiêm singleton sản xuất thông qua một hàm tạo không mặc định, cũng là hàm tạo mà bạn sử dụng để tạo ra lớp sử dụng singleton trong các bài kiểm tra đơn vị của nó.
android.weasel

11

Ứng dụng không giống như Singleton. Lý do là:

  1. Phương thức của ứng dụng (như onCreate) được gọi trong luồng ui;
  2. phương pháp của singleton có thể được gọi trong bất kỳ chủ đề nào;
  3. Trong phương thức "onCreate" của Ứng dụng, bạn có thể khởi tạo Handler;
  4. Nếu singleton được thực thi trong luồng none-ui, bạn không thể khởi tạo Handler;
  5. Ứng dụng có khả năng quản lý vòng đời của các hoạt động trong ứng dụng. Nó có phương thức "registerActivityLifecyclCallbacks". Nhưng các singletons không có khả năng.

1
Lưu ý: bạn có thể khởi tạo Handler trên bất kỳ chuỗi nào. từ doc: "Khi bạn tạo Trình xử lý mới, nó bị ràng buộc với hàng đợi luồng / thông điệp của luồng đang tạo ra nó"
Christ

1
@Christ Cảm ơn bạn! Mới đây tôi đã học được "cơ chế của Looper". Nếu khởi tạo trình xử lý trên luồng none-ui mà không có mã 'Looper.prepare ()', hệ thống sẽ báo cáo lỗi "java.lang.R nbException: Có thể 't tạo trình xử lý bên trong luồng chưa được gọi là Looper.prepare () ".
sunhang

11

Tôi đã có cùng một vấn đề: Singleton hoặc tạo một lớp con android.os.Ứng dụng?

Đầu tiên tôi đã thử với Singleton nhưng đôi khi ứng dụng của tôi thực hiện cuộc gọi đến trình duyệt

Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));

và vấn đề là, nếu thiết bị cầm tay không có đủ bộ nhớ, hầu hết các lớp của bạn (thậm chí là Singletons) đều được dọn sạch để có bộ nhớ, do đó, khi quay lại từ trình duyệt vào ứng dụng của tôi, nó sẽ bị sập mọi lúc.

Giải pháp: đặt dữ liệu cần thiết bên trong một lớp con của lớp Ứng dụng.


1
Tôi thường bắt gặp những bài đăng mà mọi người nói rằng điều này có thể xảy ra. Do đó, tôi chỉ đơn giản là đính kèm các đối tượng vào ứng dụng như singletons với tải lười biếng, v.v. để chắc chắn rằng vòng đời được ghi lại và biết. Chỉ cần đảm bảo không lưu hàng trăm hình ảnh vào đối tượng ứng dụng của bạn vì tôi hiểu rằng nó sẽ không bị xóa khỏi bộ nhớ nếu ứng dụng của bạn ở chế độ nền và tất cả các hoạt động bị phá hủy vào bộ nhớ trống cho các quá trình khác.
Janusz

Chà, Singleton lười tải sau khi khởi động lại ứng dụng không phải là cách phù hợp để cho các đối tượng bị quét bởi GC. WeakReferences là, phải không?
mschonaker

15
Có thật không? Dalvik dỡ các lớp học và mất trạng thái chương trình? Bạn có chắc chắn rằng đó không phải là việc thu gom rác các loại đối tượng liên quan đến Vòng đời giới hạn mà bạn không nên đưa vào singletons ngay từ đầu? Bạn phải đưa ra ví dụ clrar cho một yêu cầu đặc biệt như vậy!
android.weasel

1
Trừ khi có những thay đổi mà tôi không biết, Dalvik không tải các lớp. Không bao giờ. Hành vi họ đang thấy là quá trình của họ bị giết trong nền để nhường chỗ cho trình duyệt. Họ có thể đã khởi tạo biến trong hoạt động "chính" của họ, có thể chưa được tạo trong quy trình mới khi quay lại từ trình duyệt.
Groxx

5

Xem xét cả hai cùng một lúc:

  • có các đối tượng singleton như các thể hiện tĩnh bên trong các lớp.
  • có một lớp chung (Ngữ cảnh) trả về các thể hiện singleton cho tất cả các đối tượng singelton trong ứng dụng của bạn, điều này có lợi thế là các tên phương thức trong Ngữ cảnh sẽ có ý nghĩa, ví dụ: context.getLoggedinUser () thay vì User.getInstance ().

Hơn nữa, tôi khuyên bạn nên mở rộng Bối cảnh của mình để bao gồm không chỉ truy cập vào các đối tượng đơn lẻ mà một số chức năng cần được truy cập trên toàn cầu, ví dụ: context.log OfferUser (), context.readSattedData (), v.v. Có lẽ đổi tên Bối cảnh thành Mặt tiền sẽ có ý nghĩa sau đó.


4

Chúng thực sự giống nhau. Có một sự khác biệt tôi có thể thấy. Với lớp Application, bạn có thể khởi tạo các biến của mình trong Application.onCreate () và hủy chúng trong Application.onTerminate (). Với singleton, bạn phải dựa vào VM khởi tạo và hủy bỏ trạng thái.


16
các tài liệu cho onTerminate nói rằng nó chỉ được gọi bởi trình giả lập. Trên các thiết bị phương thức đó có thể sẽ không được gọi. developer.android.com/reference/android/app/NH
danb

3

2 xu của tôi:

Tôi đã nhận thấy rằng một số trường đơn / tĩnh đã được đặt lại khi hoạt động của tôi bị phá hủy. Tôi nhận thấy điều này trên một số thiết bị cấp thấp 2.3.

Trường hợp của tôi rất đơn giản: Tôi chỉ có một "init_done" riêng tư và một phương thức tĩnh "init" mà tôi đã gọi từ Activity.onCreate (). Tôi nhận thấy rằng phương thức init đã tự thực hiện lại trên một số hoạt động tái tạo lại hoạt động.

Mặc dù tôi không thể chứng minh sự khẳng định của mình, nhưng nó có thể liên quan đến KHI đơn vị / lớp được tạo / sử dụng trước tiên. Khi hoạt động bị phá hủy / tái chế, dường như tất cả các lớp chỉ hoạt động này tham khảo cũng được tái chế.

Tôi đã chuyển ví dụ của singleton sang một lớp ứng dụng phụ. Tôi nhấn mạnh chúng từ ví dụ ứng dụng. và, kể từ đó, không nhận thấy vấn đề một lần nữa.

Tôi hy vọng điều này có thể giúp đỡ một ai đó.


3

Từ miệng câu tục ngữ ...

Khi phát triển ứng dụng của bạn, bạn có thể thấy cần phải chia sẻ dữ liệu, ngữ cảnh hoặc dịch vụ trên toàn cầu trên ứng dụng của mình. Ví dụ: nếu ứng dụng của bạn có dữ liệu phiên, chẳng hạn như người dùng hiện đang đăng nhập, bạn có thể muốn tiết lộ thông tin này. Trong Android, mô hình để giải quyết vấn đề này là để cá thể android.app.Application của bạn sở hữu tất cả dữ liệu toàn cầu, sau đó coi đối tượng Ứng dụng của bạn là một đơn vị có bộ truy cập tĩnh vào các dữ liệu và dịch vụ khác nhau.

Khi viết một ứng dụng Android, bạn được đảm bảo chỉ có một phiên bản của lớp android.app.Application và do đó, nó an toàn (và được nhóm Google Android khuyên dùng) để coi nó như một singleton. Đó là, bạn có thể thêm một phương thức getInstance () tĩnh vào triển khai Ứng dụng của mình. Thích như vậy:

public class AndroidApplication extends Application {

    private static AndroidApplication sInstance;

    public static AndroidApplication getInstance(){
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
}

2

Hoạt động của tôi gọi kết thúc () (không làm cho nó kết thúc ngay lập tức, nhưng cuối cùng sẽ thực hiện) và gọi Google Street Viewer. Khi tôi gỡ lỗi nó trên Eclipse, kết nối của tôi với ứng dụng bị ngắt khi Street Viewer được gọi, mà tôi hiểu là ứng dụng (toàn bộ) bị đóng, được cho là để giải phóng bộ nhớ (vì một hoạt động đã kết thúc không nên gây ra hành vi này) . Tuy nhiên, tôi có thể lưu trạng thái trong Gói thông qua onSaveInstanceState () và khôi phục trạng thái trong phương thức onCreate () của hoạt động tiếp theo trong ngăn xếp. Bằng cách sử dụng một ứng dụng đơn hoặc phân lớp tĩnh, tôi phải đối mặt với trạng thái đóng và mất ứng dụng (trừ khi tôi lưu nó trong Gói). Vì vậy, từ kinh nghiệm của tôi, họ giống nhau về bảo tồn nhà nước. Tôi nhận thấy rằng kết nối bị mất trong Android 4.1.2 và 4.2.2 nhưng không phải trên 4.0.7 hoặc 3.2.4,


"Tôi nhận thấy rằng kết nối bị mất trong Android 4.1.2 và 4.2.2 nhưng không phải trên 4.0.7 hoặc 3.2.4, theo hiểu biết của tôi cho thấy cơ chế phục hồi bộ nhớ đã thay đổi tại một số điểm." ..... Tôi sẽ nghĩ rằng các thiết bị của bạn không có cùng dung lượng bộ nhớ khả dụng, cũng không cài đặt cùng một ứng dụng. và do đó, kết luận của bạn có thể không chính xác
Christ

@Christ: Có bạn phải đúng. Sẽ là lạ nếu cơ chế phục hồi bộ nhớ thay đổi giữa các phiên bản. Có lẽ việc sử dụng bộ nhớ khác nhau gây ra các hành vi khác biệt.
Piovezan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.