I need to have have reference to Context in my utils class.
First I am extending Application class and initializing my util class:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Utils.init(getApplicationContext());
}
}
And utils class looks like:
public class Utils{
private static Context sContext;
private Utils() {
}
public static void init(Context context) {
sContext = context;
}
}
Is there any possible way to get a leak with such approach?
I can see only one case: when application goes background - Context can be re-created, and so Utils class may be re-initialized even if it will persist in memory.
Any suggestions, please.
You should solve as follow:
public class YourClass extends Application {
private static Context context;
public void onCreate()
{
super.onCreate();
YourClass.context = getApplicationContext();
}
public static Context getAppContext() {
return YourClass.context;
}
}
How to use:
YourClass.getAppContext();
Related
I'm trying to create a singleton class that will be responsible for playing game sounds. I created a singleton class GameSounds with a method playSound(). In the res folder I have a a subfolder 'raw' with a file letter_found.mp3.
This is the source code of the GameSounds class I wrote:
import android.app.Application;
import android.content.Context;
import android.media.MediaPlayer;
public class GameSounds extends Application {
private static GameSounds gameSounds = new GameSounds();
private static MediaPlayer soundPlayer;
private static Context mContext;
private static int mySoundId = R.raw.letter_found;
private GameSounds() {
mContext = this;
}
public static GameSounds getInstance() {
return gameSounds;
}
public static void playSound() {
soundPlayer = MediaPlayer.create(mContext, mySoundId);
soundPlayer.start();
}
}
This doesn't seem to work as I'm getting the following error message:
"java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference"
I don't understand why this is happening. I tried to search Stackoverflow but couldn't find a solution.
Any help/explanation is greatly appreciated.
You can have a Singleton holding an Application Context (NOT Activity context) but practically you have to set this context before you use your singleton which can be enforced by throwing exceptions. See below example code.
public class GameSounds {
private static Context sContext;
public static void setContext(Context context) {
if (context == null) {
throw new IllegalArgumentException("context cannot be null!");
}
// In order to avoid memory leak, you should use application context rather than the `activiy`
context = context.getApplicationContext();
if (context == null) {
throw new IllegalArgumentException("context cannot be null!");
}
sContext = context;
}
private static Context getContext() {
if (sContext != null) {
return (Context)sContext;
}
throw new IllegalStateException("sContext was not set yet! Please call method setContext(Context context) first.");
}
// the rest of other methods. e.g. playSounds()
private static GameSounds gameSounds = new GameSounds();
private GameSounds() {
}
public static GameSounds getInstance() {
return gameSounds;
}
public void playSound() {
Context context = getContext();
soundPlayer = MediaPlayer.create(context, mySoundId);
soundPlayer.start();
}
}
You shouldn't inherit Application class unless you try to use Singleton pattern. Because Application is base class which contains all other components such as activities and services.
Instead, GameSound class should contain Context object and proper constructor.
Example)
public class GameSounds {
private GameSounds gameSounds;
private MediaPlayer soundPlayer;
private WeakReference<Context> mContext;
private int mySoundId = R.raw.letter_found;
private GameSounds(Context context) {
mContext = new WeakReference<>(context);
}
public GameSounds getInstance(Context context) {
if (gameSounds == null) {
gameSounds = new GameSounds(context);
}
return gameSounds;
}
public void playSound() {
soundPlayer = MediaPlayer.create(mContext.get(), mySoundId);
soundPlayer.start();
}
}
In this code, there is WeakReference<Context> instead of Context. WeakReference is used to prevent memory leaks because memory leaks can occur if you have an instance outside the activity.
To play sound, execute GameSounds.getInstance(this).playSound(); is fine.
If Context can't provide when try to play sound, implement initialize methods and called in Application class can be ok.
public class GameSounds {
private static GameSounds gameSounds;
private MediaPlayer soundPlayer;
private WeakReference<Context> mContext;
private int mySoundId = R.raw.letter_found;
private GameSounds(Application context) {
mContext = new WeakReference<>(context);
}
public static void initialize(Application context) {
if (gameSounds == null) {
gameSounds = new GameSounds(context);
}
}
public static GameSounds getInstance() {
if (gameSounds == null) {
throw new NullPointerException("You need to initialize this code by GameSound.initialize(this) in application class");
}
return gameSounds;
}
public void playSound() {
soundPlayer = MediaPlayer.create(mContext.get(), mySoundId);
soundPlayer.start();
}
}
In this case, you should make Application class and initialize GameSound class by GameSound.initialize(this) in Application class.
To play sound, GameSound.getInstance().playSound() is fine.
For instance I want to take this
public class SomeClass {
public static final String GREET_STRING = "Hello!";
//...
and change it to something like:
public class SomeClass {
public static final String GREET_STRING = getString(R.string.greet_string);
//...
Can this be done or do I need some kind of Context instantiation to get the resources for the string loading?
To use getString() you will need a context. A Resource string cannot be static final because it is possible for String resources to change as you change Locales (if you have multiple String files such as strings.xml (us) and strings.xml (uk))
Try this:
public abstract class SomeClass extends AppCompatActivity {
public static String GREET_STRING(Context context) {
if (context == null) {
return null;
}
return context.getResources().getString(R.string.greet_string);
}
}
Res/Value/String:
<resources>
<string name="greet_string">Hello!</string>
</resources>
Call SomeClass from MainClass
public class MainClass extends SomeClass {
private final static String TAG = MainClass.class.getName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// call SomeClass from MainClass
Log.i(TAG, SomeClass.GREET_STRING(this));
}
}
There are two ways access the string inside the class which is not extending Activity or Fragment.
Pass Context or Activity to class constructor
public class SomeClass {
private Context context;
public SomeClass(Context context) {
this.context = context;
}
public static final String GREET_STRING = context.getString(R.string.greet_string);
}
The second way is if you don`t want to pass context to class. You need to create an instance of the Application and static function get instance.
public class App extends Application {
private static App instance = null;
#Override
public void onCreate() {
super.onCreate();
instance = this;
}
public static App getInstance() {
// Return the instance
return instance;
}
}
public class SomeClass {
public static final String GREET_STRING = App.getInstance().getString(R.string.greet_string);
}
I made my own JUnit-Runner by implementing org.junit.runner.Runner, so that I can run my UnitTests with them using the #RunWith-Annotation.
It lookes somewhat like this:
public class MyRunner extends Runner {
private Context myContext;
myContext.init();
private final BlockJUnit4ClassRunner runner;
public MyRunner(final Class<?> clazz) throws InitializationError {
myContext = new Context();
runner = new BlockJUnit4ClassRunner(clazz);
}
#Override
public void run(final RunNotifier notifier) {
runner.run(notifier);
}
#Override
public Description getDescription() {
return runner.getDescription();
}
public void filter(final Filter filter) throws NoTestsRemainException {
runner.filter(filter);
}
}
To clean up resources, I have to shut down MyContext by calling MyContext.close(). Where should I invoke this so that my resources are cleand up after the tests have run?
I'm not sure what you're trying to achive but have you already had a look at JUnit's Rules?
public class MyContextRule extends ExternalResource {
private final Context myContext;
public MyContextRule() {
myContext = new Context();
}
#Override
protected void before() throws Throwable {
myContext.init();
}
#Override
protected void after() {
myContext.close();
}
}
Usage:
public class MyTest {
#ClassRule
public static MyContextRule contextRule = new MyContextRule();
//...
}
JUnit Rules advantage over Runners is that you can have multiple of them, while you only can have one runner.
So, your custom Rule could be used with any runner that may be introduced by a random testframework that you may come across in the future...
Where should I invoke this so that my resources are cleand up after
the tests have run ?
UPDATED MY ANSWER, you can use org.junit.runner.notification.RunListener as shown below:
(1) Create your own RunListener class:
public class MyRunnerListener extends RunListener {
private Context context;
public MyRunnerListener(Context context) {
this.context = context;
}
void testRunFinished(Result result) {
context.close();
}
}
(2) Use the MyRunnerListener inside MyRunner :
public class MyRunner extends Runner {
private Context myContext;
MyRunnerListener runnerListener;
private final BlockJUnit4ClassRunner runner;
public MyRunner(final Class<?> clazz) throws InitializationError {
myContext = new Context();
myContext.init();
runnerListener = new MyRunnerListener(myContext);
runner = new BlockJUnit4ClassRunner(clazz);
}
#Override
public void run(final RunNotifier notifier) {
notifier.addListener(runnerListener);
runner.run(notifier);
}
#Override
public Description getDescription() {
return runner.getDescription();
}
public void filter(final Filter filter) throws NoTestsRemainException {
runner.filter(filter);
}
}
P.S.: If you don't want to use the Runner, then you can follow the answer from Markus (which uses TestRule, NOT TestRunner).
How can I have access to a file in my res/raw folder from a Singleton (not an activity) on Android?
I've tried:
InputStream is = MainActivity.getResources().openRawResource("data.json");
which doesn't work since "non-static method getResouces() cannot be referenced from static content".
I've also tried:
URL fileURL = getClass().getClassLoader().getResource(R.raw.data);
String filePath = fileURL.getPath();
which throws a Null-pointer exception.
My Singleton:
public class CoursesDataManager {
private static CoursesDataManager instance;
private final List<Course> courses;
public static CoursesDataManager getInstance() {
if (instance == null)
instance = new CoursesDataManager();
return instance;
}
private CoursesDataManager() {
courses = parseCourses(**filePath/inputStream**);
}
The reason I want to get the file is that I want my Singleton to parse the data in that file once, store this data, and have this data never change and be accessible throughout the lifetime of my application.
Thanks a lot.
public class CoursesDataManager {
private static CoursesDataManager instance;
private final List<String> courses;
public static CoursesDataManager getInstance(Context context) {
if (instance == null)
instance = new CoursesDataManager(context);
return instance;
}
private CoursesDataManager(Context context) {
courses =context.getResources().openRawResource(R.raw.filename);
}
}
call it from Activity
CoursesDataManager.getInstance(getApplicationContext());
You need context to access resources. You could use Application context for this purpose. Subclass your Application, save the context in a static variable. Use the context inside singleton.
Create a Application class:
public class MyApplication extends Application {
public static MyApplication context = null;
public void onCreate() {
super.onCreate();
context = this;
}
}
In your manifest, specify the name of the Application class:
<application
android:name=".MyApplication"
...
>
</application>
Now, use this context in singleton:
InputStream is = MyApplication.context.getResources().openRawResource("data.json");
Note: You can use this method, even when you are creating the singleton instance from outside the Activity, where you dont have Activity context.
Add the context to your class and change it to something like that:
public class CoursesDataManager {
private static CoursesDataManager instance;
private Context context;
private final List<Course> courses;
public static CoursesDataManager getInstance(Context context) {
if (instance == null)
instance = new CoursesDataManager(context.getApplicationContext());
return instance;
}
private CoursesDataManager(Context context) {
this.context = context;
courses = parseCourses(**filePath/inputStream**);
}
public class CoursesDataManager {
private static CoursesDataManager instance;
private static Context mContext;
private final List<Course> courses;
public static CoursesDataManager getInstance(Context context) {
if (instance == null)
{
instance = new CoursesDataManager();
}
mContext = context;
return instance;
}
private CoursesDataManager() {
courses = parseCourses(**filePath/inputStream**);
}
You can use resource from here like
mContext.getResources().openRawResource(resourceId);
I've write a Singleton, but this singleton need a Context as a param to initialize itself. As the Context is used only once in its constructor, I would not like to add it in getInstance(Context). After thinking more, I came out the following answer:
public class Singleton {
private static Context sContext;
public static void init(Context context) {
sContext = context;
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
private Singleton() {
if (sContext == null) {
throw new IllegalArgumentException("#init should be called in Application#onCreate");
}
// Initialize the Singleton.
// .....
// After the constructed, remove the sContext.
sContext = null;
}
}
It's well, with a class method init called in Android/Applicaiton#onCreate method.
It's not instance the SingletonHolder.INSTANCE, as it's not loaded.
Could some give someone advice on my solution。Thanks!
With the help of # WarrenFaith I changed my code.
public class Singleton {
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static Singleton INSTANCE = new Singleton();
}
private Singleton() {
final Context context = BaseApplication.getApplication();
// Initialize the Singleton.
// .....
}
}
public class BaseApplication extends Application {
private static Application sApplication;
public static Application getApplication() {
return sApplication;
}
#Override
public void onCreate() {
super.onCreate();
sApplication = this;
}
}
Why not using a way easier solution:
public class Singleton {
private final static Singleton mInstance = new Singleton();
private final static Context sContext;
private Singleton() {
sContext = MyApplication.getInstance();
// do more
}
public static Singleton getInstance() {
return mInstance;
}
}
That is a pretty bullet proof singleton pattern.
Of course you need to implement your application class to be a singleton but by definition it already is.
public class MyApplication extends Application {
private static MyApplication mInstance;
#Override
protected void onCreate() {
super.onCreate();
mInstance = this;
// create your Singleton!
Singleton.getInstance();
}
public static MyApplication getInstance() {
return mInstance;
}
}