Cảnh báo: Không đặt các lớp ngữ cảnh Android trong các trường tĩnh; đây là một rò rỉ bộ nhớ (và cũng phá vỡ Instant Run)


84

Android Studio:

Không đặt các lớp ngữ cảnh Android trong các trường tĩnh; đây là một rò rỉ bộ nhớ (và cũng phá vỡ Instant Run)

Vì vậy, 2 câu hỏi:

# 1 Làm thế nào để bạn gọi a startServicetừ một phương thức tĩnh không có biến tĩnh cho ngữ cảnh?
# 2 Làm cách nào để bạn gửi một localBroadcast từ một phương thức tĩnh (giống nhau)?

Ví dụ:

public static void log(int iLogLevel, String sRequest, String sData) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(mContext, LogService.class);
        intent.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        mContext.startService(intent);
    }
}

hoặc là

        Intent intent = new Intent(MAIN_ACTIVITY_RECEIVER_INTENT);
        intent.putExtra(MAIN_ACTIVITY_REQUEST_FOR_UPDATE, sRequest));
        intent.putExtra(MAIN_ACTIVITY_DATA_FOR_VIEW, sData);
        intent.putExtra(MAIN_ACTIVITY_LOG_LEVEL, iLogLevel);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);

Cách chính xác để làm điều này mà không sử dụng là mContextgì?

LƯU Ý: Tôi nghĩ câu hỏi chính của tôi có thể là làm thế nào để chuyển ngữ cảnh cho một lớp mà từ đó phương thức gọi hoạt động.


Bạn không thể truyền Context dưới dạng tham số trong phương thức?
Juan Cruz Soler

Tôi sẽ gọi quy trình này ở những nơi không có ngữ cảnh.
John Smith

# 1 chuyển nó như một tham số # 2 giống nhau.
njzk2

Sau đó, bạn cũng phải chuyển ngữ cảnh cho phương thức người gọi. Vấn đề là các lĩnh vực tĩnh không được thu gom rác thải, vì vậy bạn có thể bị rò rỉ một hoạt động với tất cả các lần xem nó
Juan Cruz Soler

1
@JohnSmith Phân loại nó từ hoạt động khởi tạo (thông qua tham số phương thức khởi tạo hoặc tham số phương thức) đến ngay điểm bạn cần.
AndroidMechanic - Viral Patel

Câu trả lời:


56

Đơn giản chỉ cần chuyển nó dưới dạng tham số cho phương thức của bạn. Không có ý nghĩa gì khi tạo một phiên bản tĩnh Contextchỉ với mục đích bắt đầu một Intent.

Đây là cách phương pháp của bạn sẽ trông như sau:

public static void log(int iLogLevel, String sRequest, String sData, Context ctx) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(ctx, LogService.class);
        intent1.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        ctx.startService(intent);
    }
}

Cập nhật từ nhận xét về câu hỏi: Xếp tầng ngữ cảnh từ hoạt động khởi tạo (thông qua tham số phương thức khởi tạo hoặc tham số phương thức) đến ngay điểm bạn cần.


Bạn có thể cung cấp một ví dụ về hàm tạo không?
John Smith

nếu tên lớp của bạn là MyClassthêm một constructor nào giống như một phương pháp để nó public MyClass(Context ctx) { // put this ctx somewhere to use later }(Đây là nhà xây dựng của bạn) Bây giờ, tạo thể hiện mới của MyClasssử dụng này constructor ví dụMyClass mc = new MyClass(ctx);
AndroidMechanic - Viral Patel

Tôi không nghĩ rằng nó đơn giản để chuyển giao theo yêu cầu. Mặc dù có những lợi ích rõ ràng như không phải lo lắng về bối cảnh cũ hoặc như ở đây, một bối cảnh tĩnh. Giả sử bạn cần ngữ cảnh [có thể bạn muốn ghi vào prefs] trong một lệnh gọi lại phản hồi sẽ được gọi không đồng bộ. Vì vậy, đôi khi, bạn buộc phải đặt nó vào trường thành viên. Và bây giờ bạn phải nghĩ làm thế nào để không làm cho nó tĩnh. stackoverflow.com/a/40235834/2695276 dường như hoạt động.
Rajat Sharma

1
Sử dụng ApplicationContext làm trường tĩnh có được không? Không giống như các hoạt động, đối tượng ứng dụng không bị phá hủy, phải không?
NeoWang

50

Chỉ cần đảm bảo rằng bạn chuyển context.getApplicationContext () hoặc gọi getApplicationContext () trên bất kỳ ngữ cảnh nào được chuyển vào thông qua các phương thức / hàm tạo tới singleton của bạn nếu bạn quyết định lưu trữ nó trong bất kỳ trường thành viên nào.

Ví dụ bằng chứng ngu ngốc (ngay cả khi ai đó chuyển qua một hoạt động, nó sẽ lấy bối cảnh ứng dụng và sử dụng nó để khởi tạo singleton):

public static synchronized RestClient getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new RestClient(context.getApplicationContext());
    }
    return mInstance;
}

getApplicationContext () theo tài liệu: "Trả về ngữ cảnh của đối tượng Ứng dụng toàn cầu, duy nhất của quy trình hiện tại."

Điều đó có nghĩa là ngữ cảnh được trả về bởi "getApplicationContext ()" sẽ tồn tại trong toàn bộ quá trình và do đó không thành vấn đề nếu bạn lưu trữ một tham chiếu tĩnh cho nó ở bất kỳ đâu vì nó sẽ luôn ở đó trong suốt thời gian chạy ứng dụng của bạn (và tồn tại lâu hơn bất kỳ đối tượng nào / singletons do nó khởi tạo).

So sánh điều đó với bối cảnh bên trong của các khung nhìn / hoạt động chứa một lượng lớn dữ liệu, nếu bạn làm rò rỉ bối cảnh do một hoạt động nắm giữ, hệ thống sẽ không thể giải phóng tài nguyên đó rõ ràng là không tốt.

Tham chiếu đến một hoạt động theo ngữ cảnh của nó phải sống cùng một vòng đời với chính hoạt động đó, nếu không nó sẽ giữ bối cảnh làm con tin gây ra rò rỉ bộ nhớ (đó là lý do đằng sau cảnh báo xơ vải).

CHỈNH SỬA: Đối với anh chàng dựa trên ví dụ từ tài liệu ở trên, thậm chí có một phần bình luận trong mã về những gì tôi vừa viết về:

    // getApplicationContext() is key, it keeps you from leaking the
    // Activity or BroadcastReceiver if someone passes one in.

8
đối với anh chàng đánh đập anh chàng đã đánh lừa ví dụ trên: điểm của chủ đề này là cảnh báo Lint xung đột với kiểu tạo singleton được đề xuất của chính Google.
Raphael C

7
Đọc: "Không đặt các lớp ngữ cảnh của Android trong các trường tĩnh; đây là lỗi rò rỉ bộ nhớ (và cũng làm hỏng Chạy tức thì)" Bạn có biết các lớp ngữ cảnh là gì không? Activity là một trong số chúng và bạn không nên lưu trữ Activity dưới dạng trường tĩnh, như bạn đã tự mô tả (nếu không nó sẽ làm rò rỉ bộ nhớ). Tuy nhiên, bạn có thể lưu trữ Ngữ cảnh (miễn là nó là ngữ cảnh ứng dụng) dưới dạng trường tĩnh, vì nó tồn tại lâu hơn mọi thứ. (Và do đó bỏ qua cảnh báo). Tôi chắc rằng chúng ta có thể đồng ý với thực tế đơn giản này, phải không?
Marcus Gruneau

với tư cách là bác sĩ thú y iOS, trong tuần đầu tiên sử dụng Android ... Những giải thích như thế này giúp tôi hiểu ngữ cảnh này vô nghĩa .. Vì vậy, cảnh báo xơ xác đó (ồ, tôi không thích bất kỳ cảnh báo nào) sẽ lơ lửng, nhưng câu trả lời của bạn giải quyết được vấn đề thực sự .
eric

@Marcus nếu lớp con của bạn không biết ai đang khởi tạo nó với ngữ cảnh nào, thì việc lưu trữ nó dưới dạng một thành viên tĩnh là một phương pháp không tốt. hơn nữa, ngữ cảnh ứng dụng sống như một phần của đối tượng Ứng dụng của ứng dụng của bạn, đối tượng ứng dụng sẽ không ở trong bộ nhớ mãi mãi, nó sẽ bị giết. trái với suy nghĩ thông thường, ứng dụng sẽ không được khởi động lại từ đầu. Android sẽ tạo một đối tượng Ứng dụng mới và bắt đầu hoạt động mà người dùng đã ở trước đó để tạo ảo giác rằng ứng dụng đó chưa bao giờ bị khai tử ngay từ đầu.
Raphael C

@RaphaelC bạn có tài liệu về điều đó không? Điều đó dường như là hoàn toàn sai vì Android chỉ đảm bảo một ngữ cảnh Ứng dụng cho mỗi lần chạy của mỗi quá trình.
HaydenKai

6

Nó chỉ là một cảnh báo. Đừng lo lắng. Nếu bạn muốn sử dụng ngữ cảnh ứng dụng, bạn có thể lưu nó trong một lớp "singleton", được sử dụng để lưu tất cả lớp singleton trong dự án của bạn.


2

Trong trường hợp của bạn, nó không có ý nghĩa nhiều nếu nó là trường tĩnh nhưng tôi không nghĩ nó xấu trong mọi trường hợp. Nếu bây giờ bạn đang làm gì, bạn có thể có trường tĩnh có ngữ cảnh và vô hiệu hóa nó sau. Tôi đang tạo cá thể tĩnh cho lớp mô hình chính của mình có ngữ cảnh bên trong, ngữ cảnh ứng dụng của nó không phải ngữ cảnh hoạt động và tôi cũng có trường cá thể tĩnh của lớp chứa Activity mà tôi bỏ qua khi hủy. Tôi không thấy rằng tôi bị rò rỉ bộ nhớ. Vì vậy, nếu một anh chàng thông minh nào đó nghĩ rằng tôi sai, hãy bình luận ...

Ngoài ra Instant Run hoạt động tốt ở đây ...


Tôi không nghĩ rằng bạn sai về nguyên tắc, nhưng bạn cần hết sức cẩn thận rằng Activity mà bạn đang nói đến chỉ có tối đa một trường hợp duy nhất tại bất kỳ thời điểm nào trước khi nó có thể sử dụng các trường tĩnh. Nếu ứng dụng của bạn kết thúc với nhiều hơn một ngăn xếp vì nó có thể được khởi chạy từ nhiều nơi khác nhau (thông báo, liên kết sâu, ...), mọi thứ sẽ không thành công trừ khi bạn sử dụng một số cờ như singleInstance trong tệp kê khai. Vì vậy, luôn dễ dàng hơn để tránh các trường tĩnh từ Hoạt động.
BladeCoder

android: launcMode = "singleTask" là đủ, vì vậy tôi đang chuyển sang điều đó, tôi đã sử dụng singleTop nhưng không biết là không đủ vì tôi muốn luôn chỉ là các phiên bản đơn lẻ của các hoạt động chính của tôi, đó là cách ứng dụng của tôi được thiết kế.
Renetik

2
"singleTask" chỉ đảm bảo một phiên bản cho mỗi tác vụ. Nếu ứng dụng của bạn có nhiều điểm nhập như liên kết sâu hoặc khởi chạy ứng dụng từ một thông báo, bạn có thể phải thực hiện nhiều tác vụ.
BladeCoder

1

Nói chung, tránh để các trường ngữ cảnh được xác định là tĩnh. Bản thân cảnh báo giải thích tại sao: Đó là sự cố rò rỉ bộ nhớ. Mặc dù vậy, việc phá vỡ đường chạy tức thời có thể không phải là vấn đề lớn nhất trên hành tinh.

Bây giờ, có hai tình huống mà bạn nhận được cảnh báo này. Ví dụ (ví dụ rõ ràng nhất):

public static Context ctx;

Và sau đó, có một chút khó khăn hơn, trong đó bối cảnh được bao bọc trong một lớp:

public class Example{
    public Context ctx;
    //Constructor omitted for brievety 
}

Và lớp đó được định nghĩa là tĩnh ở đâu đó:

public static Example example;

Và bạn sẽ nhận được cảnh báo.

Bản thân giải pháp khá đơn giản: Không đặt các trường ngữ cảnh trong các trường hợp tĩnh , cho dù đó là một lớp bao bọc hay khai báo trực tiếp nó là tĩnh.

Và giải pháp cho cảnh báo rất đơn giản: không đặt trường tĩnh. Trong trường hợp của bạn, hãy chuyển ngữ cảnh dưới dạng một thể hiện cho phương thức. Đối với các lớp mà nhiều cuộc gọi Ngữ cảnh được thực hiện, hãy sử dụng một hàm tạo để chuyển ngữ cảnh (hoặc một Hoạt động cho vấn đề đó) đến lớp.

Lưu ý rằng đó là một cảnh báo, không phải là một lỗi. Nếu vì bất cứ lý do gì cần bối cảnh tĩnh, bạn có thể làm điều đó. Mặc dù bạn tạo ra một rò rỉ bộ nhớ khi bạn làm như vậy.


làm thế nào chúng ta có thể làm điều đó mà không tạo ra rò rỉ bộ nhớ?
isJulian00 14/02/19

1
Bạn không thể. Nếu bạn hoàn toàn cần phải vượt qua hoàn cảnh xung quanh, bạn có thể nhìn vào một xe buýt sự kiện
Zoe

Được rồi, đây là vấn đề tôi đang gặp phải nếu bạn có thể vui lòng xem nó có thể có một cách khác để làm điều đó, btw phương thức phải tĩnh vì tôi đang gọi nó từ mã c ++ stackoverflow.com/questions/54683863/…
isJulian00 14/02/19

0

Nếu bạn chắc chắn rằng đó là một Ngữ cảnh ứng dụng. Nó quan trọng nhất. Thêm điều này

@SuppressLint("StaticFieldLeak")

1
Tôi không khuyên bạn nên làm điều này anyway. Nếu bạn cần ngữ cảnh, bạn có thể sử dụng phương thức requestContext (), nếu bạn sử dụng AndroidX libs. Hoặc bạn có thể truyền trực tiếp Context đến phương thức cần nó. Hoặc bạn thậm chí có thể chỉ lấy tham chiếu lớp của ứng dụng, nhưng tôi không nên sử dụng đề xuất SuppressLint như vậy.
Oleksandr Nos

0

Sử dụng WeakReferenceđể lưu trữ Ngữ cảnh trong các lớp Singleton và cảnh báo sẽ biến mất

private WeakReference<Context> context;

//Private contructor
private WidgetManager(Context context) {
    this.context = new WeakReference<>(context);
}

//Singleton
public static WidgetManager getInstance(Context context) {
    if (null == widgetManager) {
        widgetManager = new WidgetManager(context);
    }
    return widgetManager;
}

Bây giờ bạn có thể truy cập Ngữ cảnh như

  if (context.get() instanceof MainActivity) {
            ((MainActivity) context.get()).startActivityForResult(pickIntent, CODE_REQUEST_PICK_APPWIDGET);
        }
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.