Losing 'MediaPlayer' (& other Variables) when Device is Rotated - java

I'm creating a music player for Android and it's mostly working. The problem is that when I turn the device horizontally I lose all the variables from the Activity (which makes sense because it is destroyed and re-created).
I've tried using bundles to store the state of the player with onSaveInstanceState & onRestoreInstanceState but I still can't access the media player. Is there a way to pass objects like the MediaPlayer in bundles? Should I be using a database instead?
Thanks

You should use a Service to Provides "background" audio playback capabilities, allowing the
user to switch between activities or Rotate device without stopping playback.
Check out android_packages_apps_Music which is opensource by CM on github , It use MediaPlaybackService extends Service to do this , checkout MediaPlaybackService.java

For objects you couldn't pass via a bundle, I would suggest you to use the simple SharedPreference to store objects.
Here you have a simple implementation:
public class Data {
private SharedPreferences preferences;
private int test;
public Data (Context context)
{
preferences = context.getSharedPreferences("Data", 0);
test = preferences.getInt("test", 0);
}
public int getTest()
{
return test;
}
public void setTest(int input)
{
this.test = input;
SharedPreferences.Editor editor = preferences.edit();
editor.putInt("Test", input);
editor.commit();
}
}
You have just to initialize the variable in the onCreate():
Data mydata = new Data(this);
And you can use set/get with mydata to store/retrieve your persistent data.
Edit: It is maybe not suitable for MediaPlayer objects, but for other classical types (int, string, boolean...).

Both of the methods below would allow you to keep your mediaplayer object through the rotation, but neither use bundles.
You could persist your media player by using onRetainNonConfigurationInstance() to save the variable and getLastNonConfigurationInstance() to retrieve it after the rotation, but this method isn't necessarily the best as it is not always called
-See this SO post for more info https://stackoverflow.com/a/3916068/655822
Or you could persist your media player by extending your application class and storing it in there
below info copied from the linked SO answer for the purpose of making this answer quicker to read
You can pass data around in a Global Singleton if it is going to be used a lot.
public class YourApplication extends Application
{
public SomeDataClass data = new SomeDataClass();
}
Then call it in any activity by:
YourApplication appState = ((YourApplication)this.getApplication());
appState.data.UseAGetterOrSetterHere(); // Do whatever you need to with the data here.
-See this SO post for more info on that https://stackoverflow.com/a/4208947/655822

Another way would be to :
In your AndroidManifest.xml, find your entry for your activity and add the following attribute and value:
android:configChanges="orientation|screenSize"
This will stop your activity from being destroyed and recreated on orientation.

Related

Application architecture: ViewPager2, Fragments and MutableLiveData

Good evening,
I am new at Android App development. I have a project where I receive data from multiple sensors which are connected an instrument.
At this time, I can select any sensor and stream its data using AudioTrack. The streaming is from a service and this service is bound to the MainActivity instance. A single sensor data is streamed at the time. This is working as expected.
Now, I want to plot (using MPAndroidChart) the data for each sensor in its own chart. In the activity_main layout, I added a ViewPager2 element, and Fragments are instantiated by the PagerAdapter::CreateFragment(int position) method.
My class model is as follow (based on a ViewPager2 example):
public class MainActivity extends AppCompatActivity
public class PagerAdapter extends FragmentStateAdapter
public class ChannelFragment extends Fragment
public class SensorViewModel extends ViewModel
I tested the visualisation of the plots in the Fragments using mock data (and a different colour background). The mock data was generated in ChannelFragment::onCreateView() method. This also works, though the mock data is the same for all fragments/charts, but different background colour for each fragment.
In a live test, I also proved to myself that I process changing the visible fragment correctly. I associate each fragment to a specific sensor and the streaming data changes as the visible fragment changes. This is done from MainActivity using ViewPager2.OnPageChangeCallback onPageSelected(position)
I am able to set the sensor data to the SensorViewModel and trigger a notification when calling postValue() from SensorViewModel.
The SensorViewModel contains the following declaration
private MutableLiveData<float[][]> sensorLiveData = new MutableLiveData<>();
and is created in MainActivity::onCreate(Bundle savedInstanceState)
// Create a ViewModel to hold the audio (sensor) data. This view model is used to communicate
// with the UI, graphically display the audio data for example
sensorViewModel = new ViewModelProvider(this).get(SensorViewModel.class);
The code for the observer is defined as a ChannelFragment method and listed as
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
sensorViewModel = new ViewModelProvider(requireActivity()).get(SensorViewModel.class);
sensorViewModel.getSampleData().observe(requireActivity(), new Observer<float[][]>() {
#Override
public void onChanged(#Nullable float[][] sampleData)
{
// Update the sample data graphical representation
Log.d(TAG, String.format("ChannelFragment; onChanged() sampleData length : %d, size: %d",
sampleData.length, sampleData[0].length));
addEntry(sampleData);
}
});
And the log statement reports
ChannelFragment: ChannelFragment; onChanged() sampleData length : 8, size: 170
I am stuck as to update the correct chart (correct ChannelFragment) with its corresponding sensor data.
How to a plot sampleData[0][] in Fragment at position 0, sampleData1 in Fragment at position 1, and so forth?
Calls to ChannelFragment.getId() always returns 0, though I am able to access other attributes such as title when different fragments become visible.
I also modified the SensorVIewModel as suggested by Tiago Redaelli. The new declaration:
private HashMap<Integer, MutableLiveData<ArrayList<Float>>> sensorLiveData = new HashMap<>();
where the key identifies the sensor/fragment the MutableLiveData belongs to. However, the example does not explain how to set (inject?) the ViewModel (and MutableLiveData value) to the ChannelFragment. In the example, the method setViewModel() is implemented but call is not shown.
Using this new ViewModel definition, how do I associate each HashMap values with their corresponding Fragment (who is calling setViewModel() ? What I am missing?
Thanks in advance
Daniel
Just a quick update. I was trying to create a complicated solution to my issue, including looking at ViewModel factory for example.
In my solution, I simply call setViewModel() (mentioned above) using the position/index value passed as argument when creating an instance of ChannelFragment.
This appears to be working.

How can I access methods from my Main Activity in my Settings activity?

I am currently developing a Voice Recorder app for Android. I am trying to access a few methods in my MainActivity from my Settings activity, in order to change some settings for my MediaRecorder.
I have the method below, which sets up the Audio Settings for the recording, in my MainActivity.
// set up all audio settings
private void setAudioSettings() {
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
mediaRecorder.setAudioSamplingRate(44100);
mediaRecorder.setAudioEncodingBitRate(96000);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
}
In my Settings activity, I have a standard preferences screen that I would like to show options to change the audio codec, sampling rate, etc. of the media recorder in MainActivity.
How can I access the setAudioSettings method from MainActivity here in order to do so?
If you need to see more code or screenshots, please let me know.
Make that method as static so you can call without creating the class object
public static void yourMethod(){
//Write your code here
}
And call your method like this way:
MainActivity.yourMethod();
The short answer is you should not use the functions of your one activity into another activity.
For your case, I would suggest you to have a singleton object or shared preference to store your data of settings screen. Then in onStart of MainActivity, read the singleton object or shared preference and call #setAudioSettings method accordingly.
save setting i.e values in shared preferences and then get from preferences in Main Activity.
You can make your method static by:
public static void setAudioSettings() {
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
mediaRecorder.setAudioSamplingRate(44100);
mediaRecorder.setAudioEncodingBitRate(96000);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
}
But to do that mediaRecorder needs to be static also.
Later you can call this method from any activity by:
MainActivity.setAudioSettings();
You can learn more about static keyword for example here.
But, I am not sure that use of static method is the best solution for exactly your problem, maybe will be better to set SharedPreferences in your SettingActivity and later in onResume() of your MainActivity call setAudioSettings() method and get there values from SharedPreferences?

Another way that you can exchange data between activities

What is different between static field and other ways in store data when the application is run?
I'm using static fields for pass data between activities and worked perfectly:
I defined this class :
public class Info
{
public static int ID = 0;
public static String NAME = "TEST";
public static TestClass testclass = null;
}
and I can store my data anywhere:
Info.ID = 5;
Info.NAME = "USER!";
Info.testclass = new TestClass();
and I can get my data anywhere:
Info.ID
Info.NAME
Info.testclass
It is usual to pass data between activities using extras within the intent. Such data persists for the lifetime of the receiving activity (when finished with, the garbage collector can free up the memory).
Or you can store values using SharedPreferences. These will persist between sessions and are stored as key/value pairs in a file (so don't impact memory use as such).
Or you can hold values in static fields (as you are doing here) which persist for the lifetime of your application session. However there is a significant risk with this in that the garbage collector cannot free up memory that is referenced by such fields unless you set them to null when you no longer need the reference. You should never store a reference to an activity/context/view in a static field since you'll leak the entire activity which can amout to a significant amount of memory usage.
http://android-developers.blogspot.fr/2009/01/avoiding-memory-leaks.html
You can pass a class instance within an intent if it is Serializable, e.g.:
Intent intent = new Intent(this, whatever.class);
Bundle b = new Bundle();
b.putSerializable("data", my_object);
intent.putExtras(b);
startActivity(intent);
And in the receiving activity, cast it back to whatever class your object is:
Bundle b = getIntent().getExtras();
my_object = (whatever class is it) b.getSerializable("data");
Many java classes implement Serializable and it is very simple to make your own classes serializable too.
If you're changing activities I'm assuming you're using intents. What you can do is send data with the intent with myIntent.putExtra("some string",someString);.
Then you can receive the info in your new activity using
Intent intent = getIntent();
String someString = intent.getExtra("some string");
You can use intents for passing data between activities.
Your first Activity.java
public void onClick(View v)
{
Intent timer = new Intent (FirstActivity.this,SecondActivity.class);
timer.putExtra("beefType", 5000);
startActivity(timer);
}
Then in your SecondActivity.java file do:
nt beefType = getIntent().getIntExtra("beefType", -1);
You want to share data between activities,you can use intent or shared prefernce.
The difference in using these tow and static data is that,intent and shared prefrence ,at some static data can be empty or null.but sending data using above two methods gurantees that you will get data in next activity ,unless you forcefully remove preference
you can refer this link for more info Static class in Java (Android) - use or not use
There is something called an Application Class in android. Its like a global singleton.
In other words, that Application Class will be common for that entire application.
Application class will be that first class to load.
So it will be easier to store some randomly used values in the application class.
public class Info extends Application
{
public static int ID = 0;
public static String NAME = "TEST";
}
Then call it in any activity by:
Info info= ((YourApplication)this.getApplication());
And in your manifest:
<application
android:name="<your package name>.GlobalApplication">
........
........
</application>
Well, that way doesn't always work in Android. Your static values are hold only while your app is running. Imagine you are sharing some content with action SEND, so you are starting another app which actually share your content (Facebook, email, etc.). Android may decide to stop completely your app if there are no resources enough to launch other app. In that point, the process of your app is completely gone and, with it, your static values. When going back to your app from the app that shared the content, you've lost your values.
I'd better use Intent extras, combined with Parcelable objects if you need to serialize more complex data.
You can easily try it if you enable in a device the option Don't keep activities under developer options, which destroys every activity as soon as the user leaves it.

Android Application class - lifecycle of field members

I have Android application and own Application derived class holding some internal data.
Among other there are some string fields. The problem is that if I put the application in foreground, work on other application, switch back to my app again, the app may be restarted because it got killed by system. Unfortunatelly the Application object seems not to be created again because the onCreate method of application object doesn't get called and all fields are set to null. My Activity gets recreated but all Application's object fields are null. When is the Application.onCreate method called? How to handle it?
there is no onCreate that you can register to.in later API's there's a way to register to the Activity lifecycle functions. and then you can do what ever you want.
basically, what you should do is use SharedPrefrences for storing information.
what I would do is:
class MyApp extends Application {
private static String someResource = null;
public static String getSomeResource(Context context) {
if(someResource == null) {
SharedPrefrences prefs = (SharedPrefrences)
context.getSystemService(Context.SHARED_PREFRENCES);
someResource = prefs.getString(SOME_RESOURCE, null);
}
return someResource;
}
Application onCreate() will called only for one time during its life-cycle, i.e.. only when application is started.
As suggested by thepoosh below answer is valid ,if your application is killed,still the data is saved in shared preference.

Android Thread Safe SharedPreferences

I'm not sure if I actually need this right now but if my app ever expands I could see the possibility. I basically have a wrapper around SharedPreferences that pulls a few values out of SharedPreferences and bundles them into an object. It also takes an object and uses it to update preferences. I wanted to make it thread-safe, but I wanted to try it with a Semaphore. My SharedPreferences wrapper will get a reference to the class below from getSyncedPrefManager(). It will then call aquireLock() followed by getPref(), do its work and then call releaseLock(). Does this look like something that would work or am I way off base?
public class SyncedPreferenceManager {
private final static SyncedPreferenceManager me =
new SyncedPreferenceManager();
private SharedPreferences prefs;
private static Semaphore mutex;
public static SyncedPreferenceManager getSyncedPrefManager(){
return me;
}
private SyncedPreferenceManager(){
mutex = new Semaphore(1, true);
}
public SharedPreferences getPref(Context caller){
if(prefs == null)
prefs = PreferenceManager.getDefaultSharedPreferences(caller);
return prefs;
}
public boolean aquireLock(){
try {
mutex.acquire();
} catch (InterruptedException e) {
return false;
}
return true;
}
public boolean releaseLock(){
mutex.release();
return true;
}
}
You might not like this answer.
You are not using the right system here. SharedPreferences is for storing simple preferences. Just because you can do this does not mean you should. You're basically trying to make SharedPreferences into something its not. You can add all this fancy locking but it won't stop someone from coming in underneath this later and accidentally blowing it up.
If you find yourself needing these feature in earnest, you should look at just using sqlite directly. There is little doubt you could add synchronization to SharedPreferences (and I am sure it is safe to some degree as it is already designed with a transaction/commit model) but it seems to me like reinventing the wheel.
Syncronized sections shall be enough for this purpose. Usually there are no performance concerns in saving / loading values from preferences (there are not that many of values in there).
And I also doubt, that you need it at all. Preferences are usually loaded on activity startup or saved on pause ( which is kind of songle threaded anyway, only one of activities in your application is being started or stopped at the time )
In my apöications preferecens are read eagerly, and saved only when dedicated settings activity is paused. I do not need any mutual exclusionin this case.
I also developed small wrapper library, which allows easy marshalling / unmarshalling preferences into object properties:
https://github.com/ko5tik/andject
You can always make your changes in the SharedPreferences.Editor and use apply() to apply the changes atomically.
editor.apply() is available from API level 9.
Documentation here: http://developer.android.com/reference/android/content/SharedPreferences.Editor.html#apply()

Categories

Resources