My goal is to have a thread running that plays a sound then chooses a random animation and a random image and displays them.
It is currently working, but I was wondering if there is a better way. I have a Hacker's understanding of threading (as in, I only know that this works), so I'd appreciate some feedback. Also, I've been having issues with memory overflow in my app, is there a better way to manage this Activity memory-wise? Thank you so much!
public int[] images = {R.drawable.splat0,R.drawable.splat1,R.drawable.splat2,R.drawable.splat3,
R.drawable.splat4,R.drawable.splat5,R.drawable.splat6,R.drawable.splat7,R.drawable.splat8,
R.drawable.splat9};
public int[] anims= {R.anim.splat0,R.anim.splat1,R.anim.splat2,
R.anim.splat3,R.anim.splat4,R.anim.splat5,R.anim.splat6};
MediaManager mp;
Handler tick_Handler = new Handler();
MyThread tick_thread = new MyThread();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
MainActivity.this.setContentView(R.layout.activity_main);
mp = new MediaManager();
image = (ImageView)this.findViewById(R.id.mainActivitySplat);
tick_Handler.post(tick_thread);
}
#Override
public void onStop(){
tick_Handler.removeCallbacks(tick_thread);
super.onStop();
}
#Override
public void onResume(){
tick_Handler.post(tick_thread);
super.onResume();
}
private class MyThread implements Runnable {
#Override
public void run() {
mp.playSoundClip(MainActivity.this,R.raw.swoosh);
image.setBackgroundResource(images[(int)(Math.random()*splats.length)]);
Animation myAnim=AnimationUtils.loadAnimation(MainActivity.this,splatAnim[(int)(Math.random()*splatAnim.length)]);
splat.startAnimation(myAnim);
tick_Handler.postDelayed(tick_thread, 3500);
}
}
Edit:
I have discovered this is a BAD way of using the Thread. MyThread holds an implicit reference to the Activity, and causes a massive memory leak. By changing the class to private static MyThread I solve the leak, but I have not yet figured out how to get the desired behavior this way. Will update later.
use a flag like
boolean isActibityKilled=true //when in onstop
use it in the runnable to check if activity is running or not if activity is not running , or it is stopped then kill your thread
Related
I'm using code that looks like this :
_thread = new Thread(){
#Override
public void run() {
try {
while (true) {
operate();
Thread.sleep(DELAY);
}
} catch (InterruptedException e) {
// Doesn't matters...
}
}
};
operate function looks like this :
// does things....
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
// adds an ImageView to the screen
}
});
// does other things...
At the bottom line, what i wanted to achieve is an operation that happens once in a while, without interrupting the main thread and the UI, something like a game-loop.
In the first 2 times that operate() runs, it adds the ImageView and everything is alright, but after 2 or 3 times it stops adding the ImageViews, but the UI is still running as usual. When i debugged the problem, i found out that after 3 times the run() method of the Runnable isn't called anymore, even thought the operate function was called.
The wired thing (for me) was that when i removed the Thread.sleep, everything worked fine (much faster of course...). I tried to replace it with a very long for loop (just for checking) and it worked, but of course it is not an appropriate solution to the problem.
I read about the problem, most of the people that asked this question did a thread.sleep or an infinite loop on the main thread, but, as i see it, i didn't do such thing. Many people wrote that you should replace the Thread.sleep with Handler.postDelayed. I tried to do it but it didn't work, maybe I did it wrong. I even tried replacing the runOnUiThread with other options I found on the internet, but all of them gave me the same exact results. I tried to replace the method that I'm adding the view to the activity, but all of them, again, gave the same result.
The waiting is crucial for this application. I got to find a way to wait sometime and then execute a function on the UI thread, cause this pattern returns at least a couple of times in my application.
It sounds like you want a post delay so that you can do the code on the UI thread after some delay. Handler Post Delay.
private static final int DELAY = 500;
private Handler mHandler;
private Runnable mRunnable;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private void start()
{
mHandler = new Handler();
mRunnable = new MyRunnable(this);
mHandler.postDelayed(mRunnable, DELAY);
}
private void stop()
{
mHandler.removeCallbacks(mRunnable);
}
private void doSomething()
{
// Do your stuff here.
// Reschedule.
mHandler.postDelayed(mRunnable, DELAY);
}
Recommended way of creating a Runnable.
private static class MyRunnable implements Runnable
{
private WeakReference<MainActivity> mRef;
// In here you can pass any object that you need.
MyRunnable(MainActivity activity)
{
mRef = new WeakReference<MainActivity>(activity);
}
#Override
public void run()
{
// Safety check to avoid leaking.
MainActivity activity = mRef.get();
if(activity == null)
{
return;
}
// Do something here.
activity.doSomething();
}
}
There could be several reasons why the UI Runnable isn't being executed. Probably the activity variable has something messed up with it or it's referencing the context incorrectly, or as you said the Thread.sleep() could be causing an issue. At this point more parts of the code needs to viewed to better solve the problem.
A better way of implementing your logic is to use a scheduled Timer instead of using an infinite loop with a Thread.sleep() in it. It will execute the code within a background thread. And then use a Handler to update the UI instead of activity.runOnUiThread(). Here's an example:
// Global variable within the activity
private Handler handler;
// Activity's onCreate()
#Override
protected void onCreate(Bundle savedInstanceState) {
handler = new Handler(getMainLooper());
Timer timer = new Timer("ScheduledTask");
// Timer must be started on the Main UI thread as such.
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
operate();
}
}, 0L, DELAY);
}
private void operate() {
// does things in background....
handler.post(new Runnable() {
#Override
public void run() {
// adds an ImageView to the screen from within the Main UI thread
}
});
// does other things in the background...
}
I was testing this code to check if app crashes for changing ui component from background thread. but it didn't.
Here in the code added below. I started a new thread in onCreate() method of MainActivity and It should have crashed as per the android docs which says
In the class, the Runnable.run() method contains the code that's
executed. Usually, anything is allowable in a Runnable. Remember,
though, that the Runnable won't be running on the UI thread, so it
can't directly modify UI objects such as View objects.
So I was expecting it to crash. Which it didn't. See code -
public class MainActivity extends AppCompatActivity {
TextView txt;
Thread thread;
Runnable runnable = new Runnable() {
#Override
public void run() {
txt.setText("bro");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txt = (TextView) findViewById(R.id.name_txt);
thread = new Thread(runnable);
thread.start();
}
}
While if i try changing ui while starting thread from onClicklistener() as below it does crash. which is expected.
public class MainActivity extends AppCompatActivity {
TextView txt;
Thread thread;
Runnable runnable = new Runnable() {
#Override
public void run() {
txt.setText("bro");
}
};
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View v) {
thread.start();
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txt = (TextView) findViewById(R.id.name_txt);
thread = new Thread(runnable);
txt.setOnClickListener(listener);
}
}
Now, that the second code snippet crashes, which is expected and the first one doesn't.
Please explain why is this happening, as I'm creating a new worker thread each time but just at different places. Official docs reference will be appreciated.
I found the reason behind this behavior as pointed out by #krish in the comments on my question. The reason is that, the thread was able to make changes in the TextView object only till it was not visible on UI screen i.e not rendered. It is only after the view rendering, that any thread except the Main thread may not make changes to any UI components. I Tried using view observer to see if the view was rendered before the changes or not. which showed that changes were made before the view rendering.
Here is the code that i tried.
public class MainActivity extends AppCompatActivity {
TextView txt;
Thread thread;
Runnable runnable = new Runnable() {
#Override
public void run() {
txt.setText("bro");
Log.d("ThreadTest", "The Text was changed.");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txt = (TextView) findViewById(R.id.name_txt);
thread = new Thread(runnable);
txt.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Log.d("ThreadTest", "The TextView was rendered");
}
});
}
#Override
protected void onResume() {
super.onResume();
thread.start();
}
}
Using the code above. You'll see in the output:
The Text was changed.
The TextView was rendered
Which means text was changed before view rendering. if you try to start thread to makes changes in onGlobalLayout method. App crashes as it should.
The UI is not thread-safe see processes-and-threads, so you were just lucky you did not hit one of the many landmines waiting for you.
If you don't like relying on luck then:
You should be using:
runOnUiThread(runnable);
instead of:
thread = new Thread(runnable);
AsyncTask enables proper and easy use of the UI thread.
What I'm trying to understand is how to loop a simple beat in a given time frame using a Handler like a metronome. I've read a lot and saw a few things I can do but the best way is make a Handler, is this true? So after reading I tried something but I can't understand it exactly and it's not working right now.
EDIT:
This is what I have now, it's not crashing anymore but it isn't playing the sound like it should. What did I do wrong?
public class MainActivity extends Activity {
private MediaPlayer mpBeat;
private Handler playBeatHandler = new Handler();
private Runnable playBeatTask = new Runnable() {
public void run() {
mpBeat.start();
playBeatHandler.postDelayed(this, 500);
}
};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mpBeat = MediaPlayer.create(this, R.raw.beat);
playBeatHandler.postDelayed(playBeatTask, 500);
}
}
This is what I came across and what helped me a lot to build a steady beat!
http://code.google.com/p/android-sleep-metronome/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
playBeatHandler.postDelayed(playBeatTask, 0); // now works
}
I'm updating my game using a Handler posting a delayed Runnable.
public class Example implements Runnable
{
Handler handler;
public Example()
{
handler = new Handler();
handler.postDelayed(this,10);
}
public void run()
{
handler.postDelayed(this,10);
}
}
Whenever I press the lock screen button and then resume the game, it runs much faster than it is supposed to. Every time I lock the screen and resume, it runs faster and faster. However, if I restart the activity or finish and then re-open the activity, it runs at normal speed again. Help please.
Thanks in advance.
What seems to be happening is every time you lock your screen and then resume it is making another Runnable on top of the Runnable you already have doubling your run thus making the thread go faster and faster everytime, you need to somehow pause your thread something like thread.sleep() or something similar when you lock your screen so when you resume you aren't recreating a new Runnable and instead just starting from where you left off in your thread.
If you are making a new Runnable in an onCreate method. It is going to get called anytime the phone is rotated, or when the phone resumes etc. and thus is why you are probably having this issue. The reason it doesn't happen after you finish() your activity or close your app is because when you restart the app, that Runnable is going to get created once, until you start locking the phone and resuming again etc.
you may also want to look at an inner class you can use to help handle your threads:
public class YourClass extends Activity {
public void yourUpdateMethod() {
runOnUiThread(new Runnable() {
public void run() {
new YourUpdateClass().execute(0, null, null);
}
});
}
private class YourUpdateClass extends AsyncTask<Integer, Void, Void> {
#Override
protected synchronized Void doInBackground(Integer... index) {
return null;
}
#Override
protected synchronized void onPostExecute(Void result) {
//your statements here
}
}
}
This might help handle threads that have to be paused/restarted/resumed or whatever a little better. More info here: http://developer.android.com/reference/android/os/AsyncTask.html
Dunno how it would work in a game though, didn't play around with that.
Good Luck, hope this helps.
I have an Android app which needs to play a sound resource on a timed interval, say every 120 seconds.
I know how to access the sound resources and play them, however it's time timer part I'm not sure of. What's the best approach?
I ended up ditching the Timer approach and used a Handler instead after stumbling upon an example at android-labs.
So, instead of spawning the Timer and TimerTask in my Activity's onCreate, I declared this private Handler with in-line Runnable:
private Handler _playRandomSoundHandler = new Handler();
private Runnable _playRandomSoundTask = new Runnable() {
public void run() {
SoundManager.playRandom();
_playRandomSoundHandler.postDelayed(_playRandomSoundTask, Prefs.getDelayInMilliseconds(getApplicationContext()));
}
};
Also, I had to add code to my Activity's onResume and onPause:
#Override
protected void onResume() {
super.onResume();
_playRandomSoundHandler.postDelayed(_playRandomSoundTask, Prefs.getDelayInMilliseconds(this));
}
#Override
protected void onPause() {
super.onPause();
_playRandomSoundHandler.removeCallbacks(_playRandomSoundTask);
SoundManager.stop();
}
This approach seems to work great.