Calling handler.removeCallbacks from onDeleted method in a widget throws a nullpointerexception - java

I need to stop the handler when the widget is removed by the user but calling handler.removeCallbacks throws a nullpointerexception from the onDeleted method. I tried other workarounds like creating a method,in a class which implements runnable, to kill the runnable but this throw a nullpointerexception also.
Maybe handler gets null after the call of the onDeleted method so I tried to put it in the onDisabled method but nothing stop.
What am I doing wrong?
Here the code :
public class RAMWidget extends AppWidgetProvider {
private PieGraph pg;
private Context context;
private RemoteViews remoteViews;
private AppWidgetManager appWidgetManager;
private ComponentName widget;
private Handler handler;
private CustomRunnable runnable;
#Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
this.context=context;
this.appWidgetManager=appWidgetManager;
remoteViews=new RemoteViews(context.getPackageName(),R.layout.widget_ram);
widget=new ComponentName(context,RAMWidget.class);
new DrawTask().execute();
handler=new Handler();
runnable=new CustomRunnable();
handler.postDelayed(runnable,3000);
}
private class CustomRunnable implements Runnable
{
private boolean stop;
public CustomRunnable()
{
stop=false;
}
#Override
public void run()
{
new DrawTask().execute();
Log.i("STOP",stop+"");
if(!stop)
handler.postDelayed(this,3000);
else
return;
Log.i("STOP",stop+"");
}
void killThread()
{
stop=true;
}
}
private class DrawTask extends AsyncTask<Void,Void, Void>
{
private PieSlice slice,_slice;
private long total=0,free=0,rate=0;
#Override
protected Void doInBackground(Void... unused)
{
RandomAccessFile reader=null;
try
{
reader=new RandomAccessFile("/proc/meminfo","r");
long[] mems=new long[4];
for(int i=0;i<4;i++)
{
String load = reader.readLine();
String[] toks = load.split(":");
mems[i] = Long.parseLong(toks[1].replace("kB","").trim());
}
total=mems[0]/1024;
free=(mems[1]+mems[2]+mems[3])/1024;
rate=(int)((float)(total-free)/total*100);
}
catch (Exception e)
{
e.printStackTrace();
}
if(reader!=null)
try
{
reader.close();
}
catch (IOException e)
{
e.printStackTrace();
}
slice=new PieSlice();
slice.setTitle("Available RAM");
slice.setColor(Color.parseColor("#99CC00"));
slice.setValue(total-free);
_slice=new PieSlice();
_slice.setTitle("Used RAM");
_slice.setColor(Color.parseColor("#FFBB33"));
_slice.setValue(free);
publishProgress();
return null;
}
#Override
protected void onProgressUpdate(Void... values)
{
pg=new PieGraph(context);
pg.measure(200,200);
pg.layout(0,0,200,200);
pg.setDrawingCacheEnabled(true);
pg.addSlice(slice);
pg.addSlice(_slice);
pg.setInnerCircleRatio(150);
for (PieSlice s : pg.getSlices())
s.setGoalValue(s.getValue());
pg.setDuration(1000);
pg.setInterpolator(new AccelerateDecelerateInterpolator());
pg.animateToGoalValues();
pg.setPadding(3);
remoteViews.setTextViewText(R.id.widget_ram_text, "Total RAM " + total + " MB");
remoteViews.setTextViewText(R.id.widget_ram_text1,"Avaiable RAM "+(total-free)+" MB");
remoteViews.setTextViewText(R.id.widget_ram_text2,"Used RAM "+free+" MB");
Bitmap bitmap=pg.getDrawingCache();
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
paint.setTextSize(18);
paint.setShadowLayer(1f,0f,1f,Color.WHITE);
Rect bounds=new Rect();
paint.getTextBounds(rate+" %",0,new String(rate+" %").length(),bounds);
int x=(bitmap.getWidth()-bounds.width())/2;
int y=(bitmap.getHeight()+bounds.height())/2;
canvas.drawText(rate+" %",x,y,paint);
remoteViews.setImageViewBitmap(R.id.graph_widget,bitmap);
appWidgetManager.updateAppWidget(widget,remoteViews);
}
}
#Override
public void onDeleted(Context context, int[] appWidgetIds) {
runnable.killThread();
handler.removeCallbacks(runnable); //both of them don't work
super.onDeleted(context, appWidgetIds);
}
#Override
public void onDisabled(Context context) {
runnable.killThread();
handler.removeCallbacks(runnable);
super.onDisabled(context);
}
}

The problem is that you can't depend on the same instance of your widget being called by Android each time, and so keeping non-static fields in your widget provider is a problem.
An easy solution would be to use static fields for handler and runnable. It looks like some of the other fields could go away too, for example PieGraph is constructed each time onProgressUpdate is called, so it could be a local. Basically you should avoid all non-static fields in a widget.

Related

How do I combine this two application class for Android Studio?

I have two application class which I want to combine in one, but provides two function, but I want it only one class so that I can call it on the application class in my manifest and get the App to produce both functions since i can not have two classes called on the application class in my manifest file in android studo.
I would like to put the AppController class in the App.Java class
Where I am confused is how to combine it since both extends different classes which java does not permit extending two classes in one.
Below is the App.java class
public class App extends MultiDexApplication implements Constants {
public static final String TAG = App.class.getSimpleName();
private RequestQueue mRequestQueue;
private ImageLoader mImageLoader;
private static App mInstance;
private ArrayList<Feeling> feelingsList;
private ArrayList<BaseGift> giftsList;
private SharedPreferences sharedPref;
private String username, fullname, accessToken, gcmToken = "", fb_id = "", photoUrl, coverUrl, area = "", country = "", city = "";
private Double lat = 0.000000, lng = 0.000000;
private long id;
private int state, allowRewardedAds = 1, admob = 1, ghost, pro, verify, balance, allowShowMyInfo, allowShowMyFriends, allowShowMyGallery, allowShowMyGifts, allowGalleryComments, allowComments, allowMessages, allowLikesGCM, allowCommentsGCM, allowFollowersGCM, allowGiftsGCM, allowMessagesGCM, allowCommentReplyGCM, errorCode, currentChatId = 0, notificationsCount = 0, messagesCount = 0, guestsCount = 0, newFriendsCount = 0;
#Override
public void onCreate() {
super.onCreate();
mInstance = this;
FirebaseApp.initializeApp(this);
sharedPref = this.getSharedPreferences(getString(R.string.settings_file), Context.MODE_PRIVATE);
this.readData();
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
try {
ProviderInstaller.installIfNeeded(this);
} catch (Exception e) {
e.getMessage();
}
}
}
// NB; I have some more codes in here which i am unable to put in full here.
}
}
Then this this the second class AppController.java
public class AppController extends Application {
private static Context mContext;
private static String mAppUrl;
public static MediaPlayer player;
public static Activity currentActivity;
#Override
public void onCreate() {
super.onCreate();
setContext(getApplicationContext());
mAppUrl = Constant.PLAYSTORE_URL + mContext.getPackageName();
setTelephoneListener();
player = new MediaPlayer();
mediaPlayerInitializer();
//AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
public static void mediaPlayerInitializer(){
try {
player = MediaPlayer.create(getAppContext(), R.raw.snd_bg);
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setLooping(true);
player.setVolume(1f, 1f);
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
public static String getAppUrl() {
return mAppUrl;
}
private static void setContext(Context context) {
mContext = context;
}
public static Context getAppContext() {
return mContext;
}
public static void playSound()
{
try {
if (SettingsPreferences.getMusicEnableDisable(mContext)&&!player.isPlaying()) {
player.start();
}else{
}
} catch (IllegalStateException e) {
e.printStackTrace();
mediaPlayerInitializer();
player.start();
}
}
public static void StopSound() {
if (player.isPlaying()) {
player.pause();
}
}
private void setTelephoneListener() {
PhoneStateListener phoneStateListener = new PhoneStateListener() {
#Override
public void onCallStateChanged(int state, String incomingNumber) {
if (state == TelephonyManager.CALL_STATE_RINGING) {
StopSound();
} else if (state == TelephonyManager.CALL_STATE_IDLE) {
} else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
StopSound();
}
super.onCallStateChanged(state, incomingNumber);
}
};
TelephonyManager telephoneManager = (TelephonyManager) getAppContext().getSystemService(Context.TELEPHONY_SERVICE);
if (telephoneManager != null) {
telephoneManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
}
}
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
static
{
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
}
Just Extend Your "AppController" class to "App" class. No need to do extra other work.
Like Below.
public class AppController extends App {
......
}
MulitdexApplication extends Application.
You could change your AppController to:
public class AppController extends MulitdexApplication
and then your App to
public class App extends AppController implements Constants

Implement AsyncTask when adding rows of data to multiple tables

I have created a screen that show a TextView and a ProgressBar. The ProgressBar represent to database creation and adding 1000++ data into SQLite using GSON. In order to achieve this, I have created three different files, LocalDBHelper (which is my db setup), GSONHandler (convert my JSON file into SQLite) and Loading (for my AsyncTask).
My problem is, that my ProgressBar is static, and it didn't show the progress of adding the data. I'm new to android development and clueless in proceeding with my code.
MainActivity.java
public class MainActivity extends AppCompatActivity implements Loading.LoadingTaskFinishedListener {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_setupdb_delete);
LocalDBHelper mydb = LocalDBHelper.newInstance(this);
//check if db exist, delete db if exist
if (doesDatabaseExist(mydb.getDatabaseName()))
this.deleteDatabase(mydb.getDatabaseName());
ProgressBar progressBar = findViewById(R.id.progressBar);
new Loading(progressBar, this, this).execute("");
}
#Override
public void onTaskFinished() {
finish();
}
public boolean doesDatabaseExist(String databaseName) {
File dbFile = this.getDatabasePath(databaseName);
return dbFile.exists();
}
}
Loading.java
public class Loading extends AsyncTask<String, Integer, Integer> {
public interface LoadingTaskFinishedListener {
void onTaskFinished();
}
private LocalDBHelper mydb = null;
private final ProgressBar progressBar;
private Context mContext;
private final LoadingTaskFinishedListener finishedListener;
public Loading(ProgressBar progressBar, LoadingTaskFinishedListener finishedListener, Context context) {
this.progressBar = progressBar;
this.finishedListener = finishedListener;
this.mydb = LocalDBHelper.newInstance(context);
mContext = context;
}
#Override
protected Integer doInBackground(String... params) {
SQLiteDatabase sqLiteDatabase = mydb.getWritableDatabase();
GSONHandler.newInstance(mContext, sqLiteDatabase);
return 1234;
}
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressBar.setProgress(values[0]);
}
#Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
finishedListener.onTaskFinished();
}
}
GSONHandler.java
public class GSONHandler {
static final String TAG = GSONHandler.class.getSimpleName();
private Context context;
private LocalDBHelper mydb;
private SQLiteDatabase sqLiteDatabase;
private static GSONHandler mGsonHandler = null;
public static GSONHandler newInstance(Context context, SQLiteDatabase sqLiteDatabase){
if (mGsonHandler == null){
mGsonHandler = new GSONHandler(context.getApplicationContext(), sqLiteDatabase);
}
return mGsonHandler;
}
private GSONHandler(Context context, SQLiteDatabase sqLiteDatabase) {
this.context = context;
this.sqLiteDatabase = sqLiteDatabase;
onCreate();
}
//assign json data to tables
private void onCreate() {
//get local json and save it to db
premiseCategoryMasterSetup();
premiseCategorySetup();
inspcTypeSetup();
inspcStatusSetup();
stateSetup();
areaSetup();
districtSetup();
analysisGroupSetup();
analysisSubGroup();
parameterSetup();
subParameterSetup();
identificationSetup();
premiseCertliSetup();
txn_premiseSetup();
prosecutionOtherSetup();
txn_layoutSectionSetup();
txn_layoutCardSetup();
txn_layoutInputFieldSetup();
}
private void areaSetup() {
Log.d(TAG, "areaSetup");
InputStream inputStream = context.getResources().openRawResource(R.raw.ref_area);
String jsonString = readJsonFile(inputStream);
Gson gson = new Gson();
List<Area> areaList = Arrays.asList(gson.fromJson(jsonString, Area[].class));
for (Area area : areaList)
{
if(sqLiteDatabase != null) {
ContentValues values = new ContentValues();
values.put(Constants.COLUMN_AREA_ID, area.getAreaId());
values.put(Constants.COLUMN_AREA_CODE, area.getAreaCode());
values.put(Constants.COLUMN_AREA_NAME, area.getAreaName());
values.put(Constants.COLUMN_ACTIVE, area.getActive());
values.put(Constants.COLUMN_FK_STATE_ID, area.getFk_stateId());
long id = sqLiteDatabase.insertOrThrow(Constants.REF_AREA_TABLE,
null, values);
}
}
}
....
private String readJsonFile(InputStream inputStream) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte bufferByte[] = new byte[1024];
int length;
try {
while ((length = inputStream.read(bufferByte)) != -1) {
outputStream.write(bufferByte, 0 , length);
}
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return outputStream.toString();
}
}
You are setting the progress of the progress bar inside onProgressUpdate method but it is never called. To call onProgressUpdate method, you need to call publishProgress method inside doInBackground method. The onProgressUpdate method is invoked on the UI thread after the call to publishProgress method.
In doInBackground() call publishProgress(); method to display and update progressbar. Hope this will Help.

How do I properly execute database calls on other threads?

I am really confused how I should be using threads in my Android applications for database interaction. There are too many resources and I don't know which to choose from. So I'm hoping to get more specific and focused advice on my particular situation so I have a starting point.
This is my database class structure, which works great so far:
public class DatabaseHelper extends SQLiteOpenHelper {
private static volatile SQLiteDatabase mDatabase;
private static DatabaseHelper mInstance = null;
private static Context mContext;
private static final String DB_NAME = "database.db";
private static final int DB_VERSION = 1;
private static final DB_CREATE_THINGY_TABLE = "CREATE TABLE blahblahblah...";
//other various fields here, omitted
public static synchronized DatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new DatabaseHelper(context.getApplicationContext());
try {
mInstance.open();
}
catch (SQLException e) {
e.printStackTrace();
}
}
return mInstance;
}
private DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
mContext = context;
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DB_CREATE_THINGY_TABLE);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
#Override
public void onConfigure(SQLiteDatabase db){
super.onConfigure(db);
db.setForeignKeyConstraintsEnabled(true);
}
public void open() throws SQLException {
mDatabase = getWritableDatabase();
}
public void close() {
mDatabase.close();
}
public long addNewThingy(String name) {
ContentValues values = new ContentValues();
values.put(DatabaseHelper.THINGY_COLUMN_NAME, name);
return mDatabase.insertWithOnConflict(DatabaseHelper.THINGY_TABLE, null, values, SQLiteDatabase.CONFLICT_IGNORE);
}
public Thingy getThingyById(long id) {
Cursor cursor = mDatabase.query(
DatabaseHelper.THINGY_TABLE, // table
new String[]{DatabaseHelper.THINGY_COLUMN_ID, DatabaseHelper.THINGY_COLUMN_NAME}, // column names
DatabaseHelper.THINGY_COLUMN_ID + " = ?", // where clause
new String[]{id + ""}, // where params
null, // groupby
null, // having
null); // orderby
cursor.moveToFirst();
Thingy thingy = null;
if (!cursor.isAfterLast()) {
String name = getStringFromColumnName(cursor, DatabaseHelper.THINGY_COLUMN_NAME);
thingy = new Thingy(id, name);
cursor.moveToNext();
}
cursor.close();
return thingy;
}
}
So any time I want access to the database I do mDatabaseHelper = DatabaseHelper.getInstance(context); and I am good to go. I don't make any explicit calls to open() or close() or anything like that. However right now I am making all my database calls on the UI thread, I believe (either in my onCreate or onCreateView methods or separate methods which don't invoke any new threads or anything).
How would I correctly make this threaded so that I am not performing database operations on the UI thread?
I figure I have to change all my database calls to basically do this:
Make any necessary edits to my database class first to ensure it will work properly in the event that multiple threads are trying to perform operations at the same time. I already tried by making my class a singleton (I think it's a singleton, anyway?) and using keywords like "volatile" and "synchronized" but maybe I am missing something somewhere.
Perform database operation in its own thread.
Somehow trigger additional code back in the appropriate function/activity/fragment that will execute once the database operation has completed.
Make this whole process versatile enough to where I can do it anywhere.
Am I making sense? Is this the right way to be going about all this? Is there a simple example you can make that can show me how to, for example, correctly do something like mThingy = mDatabaseHelper.getThingyById(id); or mDatabaseHelper.addNewThingy(someName); from a sample activity/fragment/etc using proper threading?
Simple solution using Threads
public class DatabaseHelper extends SQLiteOpenHelper {
//...
public void addNewThingyAsync(final String name, final Callback<Long> cb) {
new Thread(new Runnable(){
#Override
public void run(){
cb.callback(addNewThingy(name));
}
}).start();
}
private synchronized long addNewThingy(String name){
//implementation...
}
public void getThingyByIdAsync(final long id, final Callback<Thingy> cb) {
new Thread(new Runnable(){
#Override
public void run(){
cb.callback(getThingyById(id));
}
}).start();
}
private synchronized Thingy getThingyById(long id) {
//implementation...
}
public interface Callback<T> {
public void callback(T t);
}
}
Solution using AsyncTasks
Same as above with the following changes:
public class DatabaseHelper extends SQLiteOpenHelper {
//...
public void addNewThingyAsync(final String name, final Callback<Long> cb) {
new AsyncTask<Void, Void, Long>(){
#Override
protected Long doInBackground(Void... ignoredParams) {
return addNewThingy(name);
}
#Override
protected void onPostExecute(Long result) {
cb.callback(result);
}
}.execute();
}
//...
public void getThingyByIdAsync(final long id, final Callback<Thingy> cb) {
new AsyncTask<Void, Void, Thingy>(){
#Override
protected Thingy doInBackground(Void... ignoredParams) {
return getThingyById(id);
}
#Override
protected void onPostExecute(Thingy result) {
cb.callback(result);
}
}.execute();
}
//...
}
Calling (works with both approaches)
long mId = ...;
mDatabaseHelper = DatabaseHelper.getInstance(context);
mDatabaseHelper.getThingyByIdAsync(mId, new Callback<Thingy>{
#Override
public void callback(Thingy thingy){
//do whatever you want to do with thingy
}
});
How would I correctly make this threaded so that I am not performing
database operations on the UI thread?
Simply perform any database operations off the UI thread. A common technique involves an AsyncTask. For example:
public class GetThingyTask extends AsyncTask<Long, Void, Thingy> {
private Context context;
public AddTask(Context context){
this.context = context;
}
#Override
protected Thingy doInBackground(Long... ids) {
final long id = ids[0];
Cursor cursor = DatabaseHelper.getInstance(context).query(
DatabaseHelper.THINGY_TABLE,
new String[]{
DatabaseHelper.THINGY_COLUMN_ID,
DatabaseHelper.THINGY_COLUMN_NAME
},
DatabaseHelper.THINGY_COLUMN_ID + "=?",
new String[]{String.valueOf(id)},
null, null, null);
String name = null;
if (cursor.moveToFirst() && (cursor.getCount() > 0)) {
name = getStringFromColumnName(cursor, DatabaseHelper.THINGY_COLUMN_NAME);
}
cursor.close();
return new Thingy(id, name);
}
#Override
protected void onPostExecute(Thingy thingy) {
//Broadcast the Thingy somehow. EventBus is a good choice.
}
}
And to use it (inside, for example, an Activity):
new GetThingyTask(this).execute(id);

How to use a MediaPlayer Singleton

I am new to Android developing and am starting with a simple soundboard application. I started developing a soundboard using multiple fragments until I realized that I was using multiple instances of MediaPlayer. This is not good because I want only one sound to play at a time.
I realized that I'd have to use a MediaPlayer Singleton to solve my problem. The only problem is that I can't find many sources or examples of the MediaPlayer Singleton online.
Here's what I originally put into every "onCreateView" in each fragment:
public static class FragmentPage1 extends Fragment {
int selectedSoundId;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_page1, container, false);
final MediaPlayer player = new MediaPlayer();
final Resources res = getResources();
final int[] buttonIds = { R.id.btn1, R.id.btn2, R.id.btn3, R.id.btn4, R.id.btn5, R.id.btn6, R.id.btn7, R.id.btn8, R.id.btn9 };
final int[] soundIds = { R.raw.sound01, R.raw.sound02, R.raw.sound03, R.raw.sound04, R.raw.sound05, R.raw.sound06, R.raw.sound07, R.raw.sound08, R.raw.sound09 };
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View v) {
for (int i = 0; i < buttonIds.length; i++) {
if (v.getId() == buttonIds[i]) {
selectedSoundId = soundIds[i];
AssetFileDescriptor afd = res.openRawResourceFd(soundIds[i]);
player.reset();
try {
player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
player.prepare();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
player.start();
break;
}
}
}
};
for (int i = 0; i < buttonIds.length; i++) {
ImageButton soundButton = (ImageButton) rootView.findViewById(buttonIds[i]);
registerForContextMenu(soundButton);
soundButton.setOnClickListener(listener);
}
return rootView;
}
}
To my knowledge I'd probably put the onClickListener inside of each fragment and the MediaPlayer Singleton in a new Java class. I don't know what to do from there though.
How do I implement a MediaPlayer Singleton and how do I call it back in the fragment's "onCreateView" method?
Examples are highly appreciated and thanks!
See, Singleton is a design pattern, and it is implemented by setting the default constructor as private, then you should provide a get method from wich you can recover your object instance. Check out the example bellow:
public class Foo {
private MediaPlaye md;
private Foo () {
md = new MediaPlayer();
}
public MediaPlayer getMediaPlayer () {
if (md == null) {
new Foo();
}
return md;
}
}
In your sittuation, the best thing to do is to create a Service class that will encapsulate all the MediaPlayer methods. This is done like that because, usually, the developer wants that the player keeps playing even if the user leaves the Activity to which it is binded. In each fragment that you want to use the MediaPlayer API, you can bind the Service and use the defined interface. Take a look in the class below:
public class MusicPlayerService extends android.app.Service implements MediaPlayer.OnPreparedListener,
MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener,
ObserverSubject {
private static final int NOTIFY_ID = 1;
private List<MusicPlayerObserver> mObservers;
private MediaPlayer mMediaPlayer;
private final IBinder playerBind = new MusicBinder();;
private List<Track> mPlaylist;
private Integer mPosition;
private Boolean isRepeating;
private Boolean isShuffling;
private Boolean isPrepared;
private Boolean isPaused;
// Callback Methods______________________________________________
#Override
public void onCreate() {
...
}
#Override
public void onPrepared(MediaPlayer mp) {
...
}
#Override
public IBinder onBind(Intent intent) {
return playerBind;
}
#Override
public boolean onUnbind(Intent intent) {
mMediaPlayer.stop();
mMediaPlayer.release();
return false;
}
#Override
public void onDestroy() {
stopForeground(true);
}
#Override
public boolean onError(MediaPlayer mp, int what, int extra) {
mp.reset();
return false;
}
// UTIL METHODS__________________________________________________
private Long getCurrentTrackId() {
return mPlaylist.get(mPosition).getTrackId();
}
private Long getCurrentAlbumId() {
return mPlaylist.get(mPosition).getAlbumId();
}
// MEDIA PLAYER INTERFACE________________________________________
public void play() {
...
}
public void pause() {
...
}
public void resume() {
...
}
public void next() {
...
}
public void previous() {
...
}
public void seekTo(int pos) {
...
}
// SERVICE INTERFACE PROVIDER_____________________________________
/**
* Interface through the component bound to this service can interact with it
*/
public class MusicBinder extends Binder {
public MusicPlayerService getService() {
return MusicPlayerService.this;
}
}
}
I highly recommend that you follow this strategy of creating a MusicPlayer service. Also, I suggest you to take a look in another Design Patter called Observer. Usually, in music apps, you want to update several UI elements based on the MP state. Observer is perfect for that situation.
Hope I've helped a little.

Android - Sharing surfaceview between Activities or prevent drawing tasks to lock the main thread when switching activity

I am developing an app which uses a common header in all its activities.
The header contains a sort of a custom progress bar which indicates task completion. The "progress bar" is implemented by subclassing a SurfaceView and the drawing operations are managed by an inner ExecutorService.
The tasks which tell the "progress bar" to run a certain animation are issued by a Singleton custom AsyncTaskManager, which holds a reference to the custom SurfaceView and the current activity.
Some of the AsyncTasks the singleton manager controls are executed upon custom Activities onCreate method, hence sometimes the AsyncTaskManager notifies the progress bar to animate before the activity is actually displayed.
It can also happens that the user might choose to switch activity before the progressbar's drawing Runnable task is finished.
To better explain, this is what happens when I switch to some activities:
oldActivity tells the ExecutorService to cancel it's Future task that draws on the SurfaceView canvas.
newActivity's onCreate is triggered and issues the AsyncTaskManager
singleton to start a new AsyncTask.
The AsyncTask in its onPreExecute tells the progress bar to start drawing on its canvas.
The ExecutorService manages the drawing Runnable, which in turn
locks the SurfaceHolder
When the AsyncTask completes, in its onPostExecute method,
tells the surfaceview drawing Runnable to draw a different thing
according on the result.
The problem I am having is that SOMETIMES (not always - seems randomly but maybe it has to do with tasks threadpools), upon starting the new activity, the application skips frames xx where xx is apparently random (sometimes it skips ~30 frames, other times ~ 300, other times the app gets an ANR).
I have been trying to solve this for some days now, but to no avail.
I think the problem could be one of the following or a combination of both:
The drawing thread does not cancel/ends in a timely manner thus causing the SurfaceHolder to stay locked and thus preventing the Activity to take control of the View as it goes onPause/onResume and hence leading to the main thread skipping frames. The animation is by no means heavy in terms of computations (a couple of dots moving around) but it needs to last at least 300ms to properly notify the user.
The singleton AsyncTaskManager holds the reference to the "leaving activity"'s SurfaceView preventing the former to be destroyed until the surfaceholder is released and causing the frame-skipping.
I am more prone to believe the second issue is what is making Coreographer's angry and so this leads to the following question:
How can I share the SAME (as in the same instance) surfaceView (or any view, really) between all the activities or alternatively to allow the current instance of SurfaceView to be destroyed and recreated without waiting fot the threads to join/interrupt?
As it is now, the SurfaceView is being destroyed/recreated when switching between activities and I would have nothing against it if its drawing thread would stop as the new activity begins its lifecycle.
This is the custom AsyncTaskManager that holds a reference to the SurfaceView
public class AsyncTaskManager {
private RefreshLoaderView mLoader;
//reference to the customactivity holding the surfaceview
private CustomBaseActivity mActivity;
private final ConcurrentSkipListSet<RequestedTask> mRequestedTasks;
private volatile static AsyncTaskManager instance;
private AsyncTaskManager() {
mRequestedTasks = new ConcurrentSkipListSet<RequestedTask>(new RequestedTaskComparator());
}
public void setCurrentActivity(CustomBaseActivity activity) {
mActivity = activity;
if (mLoader != null) {
mLoader.onDestroy();
}
mLoader = (RefreshLoaderView) mActivity.getViewById(R.id.mainLoader);
}
This is what happens when an AsyncTask (RequestedTask in the above code snippet)
is executed
#Override
protected void onPreExecute() {
if (mLoader != null) {
mLoader.notifyTaskStarted();
}
}
#Override
protected Integer doInBackground(Void... params) {
//do the heavy lifting here...
}
#Override
protected void onPostExecute(Integer result) {
switch (result) {
case RESULT_SUCCESS:
if (mLoader != null) {
mLoader.notifyTaskSuccess();
}
break;
//TELLS THE SURFACE VIEW TO PLAY DIFFERENT ANIMATIONS ACCORDING TO RESULT ...
This is the CustomBaseActivity that holds the SurfaceView from which all others activities inherit.
public abstract class CustomBaseActivity extends FragmentActivity {
private volatile RefreshLoaderView mLoader;
//...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_base);
mLoaderContainer = (FrameLayout) findViewById(R.id.mainLoaderContainer);
mLoader = (RefreshLoaderView) findViewById(R.id.mainLoader);
//other uninteresting stuff goin on ...
And the code for the SurfaceView as well:
public class RefreshLoaderView extends SurfaceView implements SurfaceHolder.Callback {
private LoaderThread mLoaderThread;
private volatile SurfaceHolder mHolder;
private static final int ANIMATION_TIME = 600;
private final ExecutorService mExecutor;
private Future mExecutingTask;
public RefreshLoaderView(Context context) {
super(context);
...
init();
}
private void init() {
mLoaderThread = new LoaderThread();
...
}
#Override
public void surfaceChanged(SurfaceHolder holder, int arg1, int arg2, int arg3) {
...
mHolder = this.getHolder();
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
//uninteresting stuff here
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopThread();
}
private void stopThread() {
mLoaderThread.setRunning(false);
if (mExecutingTask != null) {
mExecutingTask.cancel(true);
}
}
private void startThread() {
if (mLoaderThread == null) {
mLoaderThread = new LoaderThread();
}
mLoaderThread.setRunning(true);
mExecutingTask = mExecutor.submit(mLoaderThread);
}
public void notifyTaskStarted() {
stopThread();
startThread();
mLoaderThread.setAction(LoaderThread.ANIMATION_TASK_STARTED);
}
public void notifyTaskFailed() {
mLoaderThread.setAction(LoaderThread.ANIMATION_TASK_FAILED);
}
public void notifyTaskSuccess() {
mLoaderThread.setAction(LoaderThread.ANIMATION_TASK_SUCCESS);
}
private class LoaderThread implements Runnable {
private volatile boolean mRunning = false;
private int mAction;
private long mStartTime;
private int mMode;
public final static int ANIMATION_TASK_STARTED = 0;
public final static int ANIMATION_TASK_FAILED = 1;
public final static int ANIMATION_TASK_SUCCESS = 2;
private final static int MODE_COMPLETING = 0;
private final static int MODE_ENDING = 1;
public LoaderThread() {
mMode = 0;
}
public synchronized boolean isRunning() {
return mRunning;
}
public synchronized void setRunning(boolean running) {
mRunning = running;
if (running) {
mStartTime = System.currentTimeMillis();
}
}
public void setAction(int action) {
mAction = action;
}
#Override
public void run() {
if (!mRunning) {
return;
}
while (mRunning) {
Canvas c = null;
try {
c = mHolder.lockCanvas();
synchronized (mHolder) {
//switcho quello che devo animare
if (c != null) {
switch (mAction) {
case ANIMATION_TASK_STARTED:
animationTaskStarted(c);
break;
case ANIMATION_TASK_FAILED:
animationTaskFailed(c, mMode);
break;
case ANIMATION_TASK_SUCCESS:
animationTaskSuccess(c, mMode);
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (c != null) {
mHolder.unlockCanvasAndPost(c);
}
}
}
}
private void animationTaskStarted(Canvas canvas) {
//do an animation here
}
private void animationCloseLoaderCycle(Canvas canvas) {
//do stuff here ...
} else {
mStartTime = System.currentTimeMillis();
mMode = MODE_ENDING;
}
}
private void queryThreadClose() {
mProgress = 0;
mMode = MODE_COMPLETING;
mRunning = false;
}
private void animationTaskFailed(Canvas canvas, int mode) {
switch (mode) {
case MODE_COMPLETING:
animationCloseLoaderCycle(canvas);
break;
case MODE_ENDING:
if (System.currentTimeMillis() - mStartTime < ANIMATION_TIME) {
//notify user task is failed
} else {
queryThreadClose();
}
break;
}
}
private void animationTaskSuccess(Canvas canvas, int mode) {
switch (mode) {
case MODE_COMPLETING:
animationCloseLoaderCycle(canvas);
break;
case MODE_ENDING:
if (System.currentTimeMillis() - mStartTime < ANIMATION_TIME) {
//notify user task is failed
} else {
queryThreadClose();
}
break;
}
}
}
public void onPause() {
stopThread();
}
public void onStop() {
stopThread();
}
public void onDestroy() {
stopThread();
}
}
Using DDMS when Coreographer warns me I'm skipping frame shows that there are usually around 30 threads (daemon and normal) running, where an asynctask, the main thread and the drawing task are waiting for something.
(Also, how can I check what are they waiting for?)
Thanks in advance for your help.
Edit: these are the main thread calls when it hangs, according to DDMS Threads view:
at hava.lang.Object.wait(Native Method)
at java.lang.Thread.parkFor(Thread.java:1205)
at sun.misc.Unsafe.park(Unsafe.java:325)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:157)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:813)
...
I solved this in the end. There was a mistake in the synchronized block:
while (mRunning) {
Canvas c = null;
try {
//mistake was here
c = mHolder.lockCanvas();
synchronized (mHolder) {
if (c != null) {
//do stuff
}
}
}
I was getting the canvas outside the synchronized block, thus causing a deadlock when the activity needed to be destroyed/recreated.
moving c = mHolder.lockCanvas(); inside the synchronized block solved this.
in the end the working code is as follows:
synchronized (mHolder) {
c = mHolder.lockCanvas();
if (c != null) {
switch (mAction) {
//do stuff
}
}
}
Thanks anyway!

Categories