I need to download thousands of objects. I need to be able to pause and resume downloads and to display the progress. I'm not good in multithreading, so I've compiled my code from different sources (I've simplified maths and UI but left logic intact):
public class DownloadActivity extends Activity {
private static final int MSG_FINISH = 1;
private static final int MSG_PROGRESS = 2;
private long total;
private ProgressBar progress;
private static DownloadThread thread;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.download);
progress = (ProgressBar) findViewById(R.id.progress);
total = 10000;
progress.setMax((int) total);
if (thread == null)
thread = new DownloadThread(progressHandler, 15000, 25000);
else
thread.setHandler(progressHandler);
((Button) findViewById(R.id.start_button)).setEnabled(thread.paused());
((Button) findViewById(R.id.pause_button)).setEnabled(! thread.paused());
((Button) findViewById(R.id.start_button)).setOnClickListener(startOnClickListener);
((Button) findViewById(R.id.pause_button)).setOnClickListener(pauseOnClickListener);
}
#Override
public void onBackPressed()
{
thread.pause();
thread = null;
super.onBackPressed();
}
private OnClickListener startOnClickListener = new OnClickListener() {
public void onClick(View v) {
((Button) findViewById(R.id.start_button)).setEnabled(false);
thread.unpause();
((Button) findViewById(R.id.pause_button)).setEnabled(true);
}
};
private OnClickListener pauseOnClickListener = new OnClickListener() {
public void onClick(View v) {
((Button) findViewById(R.id.pause_button)).setEnabled(false);
thread.pause();
((Button) findViewById(R.id.start_button)).setEnabled(true);
}
};
final Handler progressHandler = new Handler() {
public void handleMessage(Message msg)
{
switch (msg.what)
{
case MSG_PROGRESS:
if (progress != null)
{
long current = msg.getData().getLong("current");
progress.setProgress((int) current);
}
break;
case MSG_FINISH:
Button pause = ((Button) findViewById(R.id.pause_button));
if (pause != null)
pause.setEnabled(false);
break;
}
}
};
private class DownloadThread extends Thread
{
Handler handler;
long current;
long x;
long x2;
LinkedList<Long> pendingList;
Thread threadA;
Thread threadB;
Thread threadC;
Thread threadD;
boolean paused = true;
DownloadThread(Handler h, long x1, long x2)
{
current = 0;
this.x = x1;
this.x2 = x2;
pendingList = new LinkedList<Long>();
handler = h;
threadA = new Thread(this);
threadA.start();
threadB = new Thread(this);
threadB.start();
threadC = new Thread(this);
threadC.start();
threadD = new Thread(this);
threadD.start();
}
public void run()
{
while (! isInterrupted())
{
synchronized (this)
{
if (paused)
{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
}
Long l;
synchronized (pendingList)
{
if (pendingList.size() == 0)
{
x++;
if (x > x2)
{
continue;
}
l = new Long(x);
pendingList.add(l);
synchronized (this)
{
notifyAll();
}
continue;
}
else
{
l = pendingList.poll();
if (l == null)
{
synchronized (this)
{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
continue;
}
}
}
Object d = DownloadFactory.download(l);
if (d != null)
{
synchronized (DownloadActivity.this)
{
current++;
sendProgress();
}
}
else
{
synchronized (pendingList)
{
pendingList.add(l);
}
}
}
}
public void interrupt()
{
threadA.interrupt();
threadB.interrupt();
threadC.interrupt();
threadD.interrupt();
}
public synchronized boolean paused()
{
return paused;
}
public synchronized void pause()
{
paused = true;
}
public synchronized void unpause()
{
sendProgress();
paused = false;
notifyAll();
}
public synchronized void setHandler(Handler h)
{
handler = h;
sendProgress();
}
private void sendProgress()
{
Message msg = handler.obtainMessage(MSG_PROGRESS);
Bundle b = new Bundle();
b.putLong("current", current);
msg.setData(b);
handler.sendMessage(msg);
if (current == total)
handler.sendEmptyMessage(MSG_FINISH);
}
}
}
This code works fine and does everything I want but I understand that it is ugly and not correct (at least in putting nesting threads). So what is the nice and androidish way to accomplish the same task?
Ok I am not a concurrency professional but using a Thread which contains 4 other threads, too, sounds pretty evil :)
If you just want to make a download manager this is way beyond necessary work. If you use API 9 or above, check the DownloadManager.
Otherwise I recommend to use a simple Manager class that contains a queue and handles the add/remove and start/restart/stop of the downloads. If the queue has an element, I would start a AsyncTask which downloads the element and removes it from the queue if successfully downloaded. If not it tries to resume.
Related
I'm work on crate server and android client
but thread doesn't interrupted by android client
My android Client has request for the RoomList from server
and Server receive, Client Accept
and fill in my ListView used the Adapter
here's problem if Click backButton,
than RoomListAcitivity was close and thread was stop
but thread dosen't stop just alive in my app
first Enter has work on sucessfully
but Press BackButton on and re-Enter this Activity
MyApp just White, No Action
how can i stop this thread?
(and sorry for my English skill...)
i tried .interrupt() method , and handler.removeMessages(0)
but failed thread keep alive
upload this full java code just in case...
ListView roomList;
RoomAdapter roomAdapter;
Socketservice ss;
String msg,rtitle;
String msgs[];
String list[];
Thread listthread,EnterRoomThread,removeV;
boolean staterun = true;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_room);
roomList = findViewById(R.id.roomList);
roomAdapter = new RoomAdapter();
listthread = new Thread() {
public void run(){
ss.out.println("163|");
ss.out.println("100|");
try {
while (staterun == true) {
msg = ss.in.readLine();
handler.post(new Runnable() {
public void run() {
msgs = msg.split("\\|");
String protocol = msgs[0];
switch (protocol) {
case "163":
list = msgs[1].split(",");
for (int i = 0; i < list.length; i++) {
String list1[] = list[i].split("-");
String listT = list1[0];
int listC = Integer.parseInt(list1[1]);
int listI = Integer.parseInt(list1[2]);
roomAdapter.CreateRoom(listI, listT, listC);
}
roomList.setAdapter(roomAdapter);
msg = "";
msgs = null;
break;
case "200":
Intent i = new Intent(getApplicationContext(), GameWaitingActivity.class);
i.putExtra("tname", rtitle);
staterun = !staterun;
Toast.makeText(getApplicationContext(),""+listthread.isAlive(),Toast.LENGTH_SHORT).show();
startActivity(i);
finish();
break;
case "201":
Toast.makeText(getApplicationContext(), "방이 꽉 찼습니다.", Toast.LENGTH_SHORT).show();
break;
}
}
});
}
} catch (IOException e) {
}
}
};
listthread.start();
roomList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Room item = (Room) roomList.getItemAtPosition(position);
rtitle=item.getTitle();
EnterRoomThread = new Thread() {
public void run() {
ss.out.println("200|" + rtitle);
EnterRoomThread.interrupt();
}
};
EnterRoomThread.start();
}
});
}
Handler handler = new Handler();
#Override
public void onBackPressed() {
removeV = new Thread() {
public void run(){
ss.out.println("101|");
removeV.interrupt();
}
};
removeV.start();
handler.removeMessages(0);
staterun = false;
listthread.interrupt();
Toast.makeText(getApplicationContext(),""+listthread.isAlive(),Toast.LENGTH_SHORT).show();
finish();
}
}
Go ahead with this, write this inside run() method
//use this boolean value to keep track.
boolean shouldRunFlag = true;
while (shouldRunFlag) {
try {
Thread.sleep(10);
//Do your work............
} catch (Exception e) {
e.printStackTrace();
Log.v(TAG, "Interrupted Exception caught");
// change the flag, so that while loop can be broken.
shouldRunFlag = false;
Log.v(TAG, "run: breaking the loop");
break;
}
}
and this is how you interrupt and clean the thread
private void interrupt(Thread currentThread) {
Log.i(TAG, "interrupt");
if (currentThread != null) {
Thread dummyThread = currentThread;
dummyThread.interrupt();
currentThread = null;
}
}
I tried to implement a layout that works like a progress bar. I want to it to be able to be set to any level I want in special cases. For example to be set to 20.
#Override
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
mCDrawable.setLevel(mProgress);
}
}
Runnable a= new Runnable() {
#Override
public void run() {
running = true;
handler.sendEmptyMessage(0x123);
mProgress = 20;
try {
Thread.sleep(18);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
public void add(int num){
Thread s = new Thread(a);
s.start();
}
I do it by calling the "add" function.
The problem is that in a "while" where the progress changes continueslly it works:
Runnable r = new Runnable() {
#Override
public void run() {
running = true;
while (running) {
handler.sendEmptyMessage(0x123);
if (mProgress > MAX_PROGRESS) {
mProgress = 0;
}
mProgress += 100;
try {
Thread.sleep(18);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
What is the difference? How can I set it to a specific value otherwise?
---edit----:
I tried this code and it still doesnt work:
public void add(int num){
int lvl = mClipDrawable.getLevel();
Log.d("______________", "the first was "+lvl);
lvl += 100;
if (lvl > 10000) {
lvl = 1000;
}
Log.d("______________", "set to "+lvl);
mClipDrawable.setLevel(lvl);
}
the first level was 0 and it set 100..
I have two threads, two handlers. From thread i check a random number and send result to handle to update ui. But i am getting the error "only the original thread that created a view hierarchy can touch its views.". I searched some articles, they tell to use handler. I am doing that, yet can not avoid the errors.
After some checking, I found that it crashes when A sends the result. In case of B, it works
Also, can i use one handler for two thread?
public class MainActivity extends Activity implements View.OnClickListener{
Button start;
TextView status_B, status_A;
Boolean isRunning;
Thread Athread, Bthread;
int a, b;
Handler a_Handler, b_Handler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Initialize variables
start = (Button) findViewById(R.id.button);
start.setOnClickListener(this);
status_B = (TextView) findViewById(R.id.textView);
status_A = (TextView) findViewById(R.id.textView1);
a_Handler = new Handler() {
public void handleMessage(Message msg)
{
int number = msg.getData().getInt("number");
String winner = msg.getData().getString("winner");
start.setEnabled(true);
isRunning = false;
status_A.setText(winner + number);
}
};
b_Handler = new Handler() {
public void handleMessage(Message msg)
{
int number = msg.getData().getInt("number");
String winner = msg.getData().getString("winner");
start.setEnabled(true);
isRunning = false;
status_B.setText(winner + number);
}
};
}
#Override
protected void onStart() {
super.onStart();
isRunning = false;
}
#Override
public void onClick(View v) {
start.setEnabled(false);
status_B.setText("Guessing...");
if (!isRunning)
{
Athread = new Thread(new Runnable() {
#Override
public void run() {
while (isRunning)
{
try
{
Athread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
a = (int) (Math.random()*1000);
System.out.println("a "+ a);
if(a%7 == 0) {
isRunning = false;
Bundle bundle = new Bundle();
bundle.putString("winner", "A");
bundle.putInt("number", a);
Message msg = a_Handler.obtainMessage();
msg.setData(bundle);
a_Handler.handleMessage(msg);
}
}
}
});
Bthread = new Thread(new Runnable() {
#Override
public void run() {
while(isRunning)
{
try
{
Bthread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
b = (int) (Math.random()*1000);
System.out.println("b "+ b);
if(b%7 == 0) {
isRunning = false;
Bundle bundle = new Bundle();
bundle.putString("winner", "B");
bundle.putInt("number", b);
Message msg = b_Handler.obtainMessage();
msg.setData(bundle);
b_Handler.sendMessage(msg);
}
}
}
});
isRunning = true;
Athread.start();
Bthread.start();
}
}
}
You need put your code to modify views on UI thread:
a_Handler = new Handler() {
public void handleMessage(Message msg)
{
final int number = msg.getData().getInt("number");
final String winner = msg.getData().getString("winner");
runOnUiThread(new Runnable() {
#Override
public void run() {
start.setEnabled(true);
status_A.setText(winner + number);
}
});
isRunning = false;
}
};
b_Handler = new Handler() {
public void handleMessage(Message msg)
{
final int number = msg.getData().getInt("number");
final String winner = msg.getData().getString("winner");
runOnUiThread(new Runnable() {
#Override
public void run() {
start.setEnabled(true);
status_B.setText(winner + number);
}
});
isRunning = false;
}
};
I have a ball that keeps moving from start. I want to stop ball on button click. When I clicked on button it show the Toast, but the ball keep on moving. It doesn't stop.
Please guide me how to stop ball on button click. My code for Activity is there.
#SuppressLint("HandlerLeak")
public class BounceActivity extends Activity {
private static final int GAME_START = 500;
private static final int GAME_STOP = 600;
Thread myRefreshThread = null;
BounceView myBounceView = null;
int width = 0;
int height = 0;
Handler myGUIUpdateHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case BounceActivity.GAME_START:
myBounceView.invalidate();
break;
case BounceActivity.GAME_STOP:
Log.d("BounceView", "Game state: " + BounceView.game_state);
Toast.makeText(BounceActivity.this, "Game stopped", Toast.LENGTH_SHORT).show();
myRefreshThread = null;
break;
}
super.handleMessage(msg);
}
};
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
this.myBounceView = new BounceView(this, dm.widthPixels, dm.heightPixels);
this.setContentView(myBounceView);
myRefreshThread = new Thread(new RefreshRunner());
myRefreshThread.start();
}
class RefreshRunner implements Runnable {
#Override
public void run() {
while (myRefreshThread != null) {
if(BounceView.game_state == 0) {
Message msg = Message.obtain();
msg.what = BounceActivity.GAME_START;
myGUIUpdateHandler.sendMessage(msg);
} else if (BounceView.game_state == 1) {
Message msg = Message.obtain();
msg.what = BounceActivity.GAME_STOP;
myGUIUpdateHandler.sendMessage(msg);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}}
You are accessing and setting myRefreshThread on different threads, which can be problematic.
Instead of checking if myRefreshThread is null in your while loop, you can add a "running" flag to RefreshRunner, and a stop method that sets it to false.
Than check that flag in the while loop.
Hold a reference to your RefreshRunner (the one you pass to myRefreshThread), and call stop() when you want the animation to stop.
class RefreshRunner implements Runnable {
private void mRunning = true;
public void stop () {
mRunning = false;
}
#Override
public void run() {
while (mRunning) {
if(BounceView.game_state == 0) {
Message msg = Message.obtain();
msg.what = BounceActivity.GAME_START;
myGUIUpdateHandler.sendMessage(msg);
} else if (BounceView.game_state == 1) {
Message msg = Message.obtain();
msg.what = BounceActivity.GAME_STOP;
myGUIUpdateHandler.sendMessage(msg);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}}
the Problem I am having is that the PostExecute is not firing.
I see the log tag for background but P.E. never fires.
I am invoking this task from a timer like this:
findViewById(R.id.buttonstart).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
try {
openFile("FeedTimerTask.html");
Timer t = new Timer("FeedTimerTask", true);
timerTask = new FeedTimerTask();
t.schedule(timerTask, 2000, 20000);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
Runnable runme = new Runnable() {
#Override
public void run() {
timestart = Calendar.getInstance().getTimeInMillis();
provider.refreshNoCache();
}
};
class FeedTimerTask extends TimerTask{
#Override
public void run() {
try{Looper.prepare();}catch(Exception e){};
runme.run();
}
}
Here is the main task itself from inside the dataprovider class invoked with "provider.refreshNoCache();" above:
// threaded rteftesh tasks
#SuppressWarnings("rawtypes")
public class RefreshTask extends SupportAsyncTask {
private int errorcodecode = 0;
private ProgressDialog dialog=null;
private Exception mainExeption=null;
protected String waitMessage = "Laddar ner information..";
private boolean useCache;
public RefreshTask(boolean useCache) {
this.useCache = useCache;
}
public void onPreExecute() {
data = null;
if (showSpinnerOnRefresh){
dialog = ProgressDialog.show(context, "", waitMessage , true);
dialog.show();
}
}
protected Object doInBackground(Object... params) {
errorcodecode = 1;
try {
invokeFeedRead();
Log.e("DataProvider", "Bkgtask...");
errorcodecode = 0;
} catch (BrJSONException e) {
Log.e("[ERROR]","PROVIDER "+e.getMessage());
mainExeption = e;
errorcodecode = 1;
} catch (IOException e) {
Log.e("[ERROR]","PROVIDER "+e.getMessage());
mainExeption = e;
errorcodecode = 2;
} catch (Exception e) {
Log.e("[ERROR]","PROVIDER "+e.getMessage());
mainExeption = e;
errorcodecode = 3;
}
if (errorcodecode==0){
}
return null;
}
#Override
protected void onCancelled() {
super.onCancelled();
Log.e("DataProvider", "Cancelled...");
if (dialog != null)
try{dialog.dismiss();}catch(Exception e){}
BrAlert.Show(context, "Obs", BrAppConfig.ServerError+" (timeout)", 0);
onError_IO(new IOException("Timeout!"));
errorcodecode=2;
}
#Override
protected void onPostExecute(Object result) {
// super.onPostExecute(result);
Log.e("DataProvider", "PostExec...");
if (dialog != null)
try{dialog.dismiss();}catch(Exception e){}
switch (errorcodecode) {
case 0:
onFeedLoaded();
cacheAge = System.currentTimeMillis();
break;
case 1:
onError_DataFormat(mainExeption);
break;
case 2:
onError_IO(mainExeption);
break;
default:
onError_GeneralExeption(mainExeption);
}
}
}
Your task is cancelled even before it reached onPostExecte method. If the task is cancelled before it reaches onPostExecute Method. It will not trigger onPostExecute but trigger onCancelled Method. Please provide enough time to finish the task.
I found out the problem in the end. It was to do with the scope.
I needed a handler to invoke the other thread.
Here is the solution for others may find helpful:
in on create:
tickHandler = new Handler();
tickTimer = new Timer();
tickTimer.schedule(new FeedTimerTask(),
0,
50000); //FPS
The handler class.
class FeedTimerTask extends TimerTask{
private Runnable runable;
public FeedTimerTask(){
super();
runable = new Runnable(){
#Override
public void run() {
timestart = Calendar.getInstance().getTimeInMillis();
provider.refreshNoCache();
}
};
}
#Override
public void run() {
tickHandler.post(runable);
}
}