Sử dụng bối cảnh ứng dụng ở khắp mọi nơi?


476

Trong một ứng dụng Android, có bất cứ điều gì sai với cách tiếp cận sau:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

và vượt qua nó ở mọi nơi (ví dụ SQLiteOpenHelper) trong đó bối cảnh là bắt buộc (và tất nhiên không bị rò rỉ)?


23
Chỉ cần giải thích cho những người khác thực hiện điều này, sau đó bạn có thể sửa đổi <application>nút của tệp AndroidManifest.xml để bao gồm định nghĩa thuộc tính sau : android:name="MyApp". MyApp cần phải nằm trong cùng gói mà tài liệu tham khảo của bạn.
Matt Huggins

6
Cách TUYỆT VỜI để giải quyết vấn đề cung cấp ngữ cảnh cho SQLiteOpenHelper !! Tôi đã triển khai một "SQLiteManager" đơn lẻ và bị mắc kẹt tại "làm thế nào để tôi có được một bối cảnh cho singleton?"
Ai đó ở đâu đó

8
Để bạn biết rằng bạn sẽ trả lại ứng dụng của mình bằng một trong những giao diện tuyệt vời của nó, vì vậy nếu bạn cung cấp các phương thức bổ sung trong MyApp, bạn sẽ không thể sử dụng chúng. Thay vào đó, getContext () của bạn phải có kiểu trả về MyApp và theo cách đó bạn có thể sử dụng các phương thức được thêm sau này, cũng như tất cả các phương thức trong ContextWrapper và Context.

5
Xem thêm goo.gl/uKcFn - đó là một câu trả lời khác liên quan đến bài viết tương tự. Tốt hơn nên đặt biến tĩnh trong onCreate chứ không phải c'tor.
AlikElzin-kilaka

1
@ChuongPham Nếu khung đã giết ứng dụng của bạn, sẽ không có gì truy cập vào bối cảnh null ...
Kevin Krumwiede

Câu trả lời:


413

Có một vài vấn đề tiềm ẩn với cách tiếp cận này, mặc dù trong rất nhiều trường hợp (ví dụ như ví dụ của bạn) nó sẽ hoạt động tốt.

Đặc biệt, bạn nên cẩn thận khi xử lý bất cứ điều gì liên quan GUIđến yêu cầu đó Context. Ví dụ: nếu bạn chuyển Bối cảnh ứng dụng vào, LayoutInflaterbạn sẽ nhận được Ngoại lệ. Nói chung, cách tiếp cận của bạn là tuyệt vời: nên sử dụng một cách tốt Activity's Contexttrong đó ActivityApplication Contextkhi vượt qua một bối cảnh nằm ngoài phạm vi của một Activityđể tránh rò rỉ bộ nhớ .

Ngoài ra, để thay thế cho mẫu của bạn, bạn có thể sử dụng phím tắt gọi getApplicationContext()trên một Contextđối tượng (chẳng hạn như Hoạt động) để lấy Bối cảnh ứng dụng.


22
Cảm ơn cho một câu trả lời đầy cảm hứng. Tôi nghĩ rằng tôi sẽ chỉ sử dụng phương pháp này cho lớp kiên trì (vì tôi không muốn đi với các nhà cung cấp nội dung). Tự hỏi động lực đằng sau việc thiết kế SQLiteOpenHelper là gì theo cách mong đợi một Ngữ cảnh được cung cấp thay vì có được nó từ chính Ứng dụng. PS và cuốn sách của bạn là tuyệt vời!
yanchenko

7
Sử dụng bối cảnh ứng dụng với LayoutInflatorchỉ làm việc cho tôi. Phải được thay đổi trong ba năm qua.
Jacob Phillips

5
@JacobPhillips Sử dụng LayoutInflator mà không có ngữ cảnh hoạt động sẽ bỏ lỡ kiểu dáng của Activity đó. Vì vậy, nó sẽ làm việc theo một nghĩa, nhưng không phải là một ý nghĩa khác.
Đánh dấu

1
@MarkCarter Bạn có nghĩa là sử dụng Bối cảnh ứng dụng sẽ bỏ lỡ kiểu dáng của Hoạt động?
Jacob Phillips

1
@JacobPhillips có, Bối cảnh ứng dụng không thể có kiểu dáng vì mỗi Hoạt động có thể được tạo kiểu theo một cách khác nhau.
Đánh dấu

28

Theo kinh nghiệm của tôi, phương pháp này không cần thiết. Nếu bạn cần bối cảnh cho bất cứ điều gì bạn thường có thể nhận được thông qua một cuộc gọi đến View.getContext () và sử dụng kết quả Contextthu được ở đó, bạn có thể gọi Context.getApplicationContext () để lấy Applicationngữ cảnh. Nếu bạn đang cố gắng lấy Applicationbối cảnh này từ một Activitybạn luôn có thể gọi Activity.getApplication () có thể được chuyển qua khi Contextcần thiết cho một cuộc gọi đến SQLiteOpenHelper().

Nhìn chung, dường như không có vấn đề gì với cách tiếp cận của bạn trong tình huống này, nhưng khi xử lý Contextchỉ cần đảm bảo rằng bạn không bị rò rỉ bộ nhớ ở bất cứ đâu như được mô tả trên blog chính thức của Nhà phát triển Google Android .


13

Một số người đã hỏi: làm thế nào singleton có thể trả về một con trỏ null? Tôi đang trả lời câu hỏi đó. (Tôi không thể trả lời trong một bình luận vì tôi cần đăng mã.)

Nó có thể trả về null ở giữa hai sự kiện: (1) lớp được tải và (2) đối tượng của lớp này được tạo. Đây là một ví dụ:

class X {
    static X xinstance;
    static Y yinstance = Y.yinstance;
    X() {xinstance=this;}
}
class Y {
    static X xinstance = X.xinstance;
    static Y yinstance;
    Y() {yinstance=this;}
}

public class A {
    public static void main(String[] p) {
    X x = new X();
    Y y = new Y();
    System.out.println("x:"+X.xinstance+" y:"+Y.yinstance);
    System.out.println("x:"+Y.xinstance+" y:"+X.yinstance);
    }
}

Hãy chạy mã:

$ javac A.java 
$ java A
x:X@a63599 y:Y@9036e
x:null y:null

Dòng thứ hai cho thấy Y.xinstanceX.yinstancenull ; chúng là null vì các biến X.xinstance ans Y.yinstance đã được đọc khi chúng là null.

Điều này có thể được sửa chữa? Đúng,

class X {
    static Y y = Y.getInstance();
    static X theinstance;
    static X getInstance() {if(theinstance==null) {theinstance = new X();} return theinstance;}
}
class Y {
    static X x = X.getInstance();
    static Y theinstance;
    static Y getInstance() {if(theinstance==null) {theinstance = new Y();} return theinstance;}
}

public class A {
    public static void main(String[] p) {
    System.out.println("x:"+X.getInstance()+" y:"+Y.getInstance());
    System.out.println("x:"+Y.x+" y:"+X.y);
    }
}

và mã này cho thấy không có bất thường:

$ javac A.java 
$ java A
x:X@1c059f6 y:Y@152506e
x:X@1c059f6 y:Y@152506e

NHƯNG đây không phải là một tùy chọn cho Applicationđối tượng Android : lập trình viên không kiểm soát thời gian khi nó được tạo.

Một lần nữa: sự khác biệt giữa ví dụ thứ nhất và ví dụ thứ hai là ví dụ thứ hai tạo ra một thể hiện nếu con trỏ tĩnh là null. Tuy nhiên, một lập trình viên không thể tạo các đối tượng ứng dụng Android trước khi hệ thống quyết định làm điều đó.

CẬP NHẬT

Thêm một ví dụ khó hiểu khi các trường tĩnh khởi tạo xảy ra null.

Main.java :

enum MyEnum {
    FIRST,SECOND;
    private static String prefix="<", suffix=">";
    String myName;
    MyEnum() {
        myName = makeMyName();
    }
    String makeMyName() {
        return prefix + name() + suffix;
    }
    String getMyName() {
        return myName;
    }
}
public class Main {
    public static void main(String args[]) {
        System.out.println("first: "+MyEnum.FIRST+" second: "+MyEnum.SECOND);
        System.out.println("first: "+MyEnum.FIRST.makeMyName()+" second: "+MyEnum.SECOND.makeMyName());
        System.out.println("first: "+MyEnum.FIRST.getMyName()+" second: "+MyEnum.SECOND.getMyName());
    }
}

Và bạn nhận được:

$ javac Main.java
$ java Main
first: FIRST second: SECOND
first: <FIRST> second: <SECOND>
first: nullFIRSTnull second: nullSECONDnull

Lưu ý rằng bạn không thể di chuyển khai báo biến tĩnh một dòng trên, mã sẽ không biên dịch.


3
Ví dụ hữu ích; thật tốt khi biết rằng có một lỗ như vậy. Điều tôi rút ra từ điều này là người ta nên tránh đề cập đến một biến tĩnh như vậy trong quá trình khởi tạo tĩnh của bất kỳ lớp nào.
ToolmakerSteve

10

Lớp ứng dụng:

import android.app.Application;
import android.content.Context;

public class MyApplication extends Application {

    private static Context mContext;

    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
    }

    public static Context getAppContext() {
        return mContext;
    }

}

Khai báo ứng dụng trong AndroidManifest:

<application android:name=".MyApplication"
    ...
/>

Sử dụng:

MyApplication.getAppContext()

1
Dễ bị rò rỉ bộ nhớ. Bạn không bao giờ nên làm điều này.
Dragas

9

Bạn đang cố gắng tạo một trình bao bọc để lấy Bối cảnh ứng dụng và có khả năng nó có thể trả về nullcon trỏ "".

Theo hiểu biết của tôi, tôi đoán cách tiếp cận tốt hơn để gọi - bất kỳ trong số 2 Context.getApplicationContext() hoặc Activity.getApplication().


13
Khi nào nó nên trả về null?
Bị kẹt

25
Không có phương thức Context.getApplicationContext () tĩnh mà tôi biết. Tui bỏ lỡ điều gì vậy?
dalcantara

Tôi cũng thực hiện cách tiếp cận tương tự trong ứng dụng của mình, nhưng khi gọi SQLiteOpenHelper, nó sẽ trả về con trỏ null. Bất kỳ câu trả lời cho loại tình huống này.
ashutosh

2
Đây có thể là trường hợp nếu bạn gọi SQLiteOpenHelper trong một trình cung cấp nội dung được tải trước ứng dụng.
Gunnar Bernstein

5

Đó là một cách tiếp cận tốt. Tôi cũng sử dụng nó. Tôi chỉ đề nghị ghi đè onCreateđể đặt singleton thay vì sử dụng hàm tạo.

Và vì bạn đã đề cập SQLiteOpenHelper: Trong onCreate ()bạn cũng có thể mở cơ sở dữ liệu.

Cá nhân tôi nghĩ rằng tài liệu đã sai khi nói rằng thông thường không cần phải phân lớp Ứng dụng . Tôi nghĩ điều ngược lại là đúng: Bạn nên luôn luôn phân lớp Ứng dụng.


3

Tôi sẽ sử dụng Bối cảnh ứng dụng để nhận Dịch vụ hệ thống trong hàm tạo. Điều này giúp giảm bớt thử nghiệm và lợi ích từ thành phần

public class MyActivity extends Activity {

    private final NotificationManager notificationManager;

    public MyActivity() {
       this(MyApp.getContext().getSystemService(NOTIFICATION_SERVICE));
    }

    public MyActivity(NotificationManager notificationManager) {
       this.notificationManager = notificationManager;
    }

    // onCreate etc

}

Lớp kiểm tra sau đó sẽ sử dụng hàm tạo quá tải.

Android sẽ sử dụng hàm tạo mặc định.


1

Tôi thích nó, nhưng tôi sẽ đề nghị một singleton thay thế:

package com.mobidrone;

import android.app.Application;
import android.content.Context;

public class ApplicationContext extends Application
{
    private static ApplicationContext instance = null;

    private ApplicationContext()
    {
        instance = this;
    }

    public static Context getInstance()
    {
        if (null == instance)
        {
            instance = new ApplicationContext();
        }

        return instance;
    }
}

31
Việc mở rộng android.app.application đã đảm bảo cho singleton nên điều này là không cần thiết
Vincent

8
Điều gì nếu bạn muốn acess từ các lớp không hoạt động?
Maxrunner

9
Bạn không bao giờ nên newtự mình sử dụng Ứng dụng (ngoại trừ khả năng kiểm tra đơn vị). Hệ điều hành sẽ làm điều đó. Bạn cũng không nên có một constructor. Đó là những gì onCreatedành cho.
Martin

@Vincent: bạn có thể gửi một số liên kết về điều này? tốt nhất là mã - Tôi đang hỏi ở đây: stackoverflow.com/questions/19365797/
Kẻ

@radzio tại sao chúng ta không nên làm điều đó trong constructor?
Miha_x64

1

Tôi đang sử dụng cùng một cách tiếp cận, tôi đề nghị viết singleton tốt hơn một chút:

public static MyApp getInstance() {

    if (instance == null) {
        synchronized (MyApp.class) {
            if (instance == null) {
                instance = new MyApp ();
            }
        }
    }

    return instance;
}

nhưng tôi không sử dụng ở mọi nơi, tôi sử dụng getContext()getApplicationContext()nơi tôi có thể làm điều đó!


Vì vậy, xin vui lòng viết bình luận để giải thích lý do tại sao bạn đánh giá thấp câu trả lời để tôi có thể hiểu. Cách tiếp cận đơn lẻ được sử dụng rộng rãi để có được bối cảnh hợp lệ bên ngoài các hoạt động hoặc cơ thể xem ...
Seraphim's

1
Không cần vì hệ điều hành đảm bảo rằng Ứng dụng được khởi tạo chính xác một lần. Nếu có tôi sẽ đề nghị đặt Singelton trong onCreate ().
Martin

1
Một cách an toàn chủ đề tốt để lười biếng khởi tạo một singleton, nhưng không cần thiết ở đây.
naXa

2
Ồ, ngay khi tôi nghĩ mọi người cuối cùng đã ngừng sử dụng khóa kiểm tra hai lần ... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Søren Boisen
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.