I'm trying to replace deprecated AsyncTask without using Kotlin Coroutines or others libraries, so I have
MyTask objects with following structure
public abstract class MyTask<R> implements MyCallable<R> {
#Override
public void setUiForLoading() {
//runs on ui
}
#Override
public void setDataAfterLoading(R result) {
//runs on ui
}
#Override
public R call() throws Exception {
//runs in background
return null;
}
}
MyCallable is just a simple interface
public interface MyCallable<R> extends Callable<R>{
void setDataAfterLoading(R result);
void setUiForLoading();
}
And use this MyTaskRunner to execute them
public class MyTaskRunner {
private final Handler handler = new Handler(Looper.getMainLooper());
private final Executor executor = Executors.newCachedThreadPool();
public <R> void executeAsync(MyCallable<R> callable) {
try {
callable.setUiForLoading();
executor.execute(new RunnableTask<R>(handler, callable));
} catch (Exception e) {
}
}
public static class RunnableTask<R> implements Runnable{
private final Handler handler;
private final MyCallable<R> callable;
public RunnableTask(Handler handler, MyCallable<R> callable) {
this.handler = handler;
this.callable = callable;
}
#Override
public void run() {
try {
final R result = callable.call();
handler.post(new RunnableTaskForHandler(callable, result));
} catch (Exception e) {
}
}
}
public static class RunnableTaskForHandler<R> implements Runnable{
private MyCallable<R> callable;
private R result;
public RunnableTaskForHandler(MyCallable<R> callable, R result) {
this.callable = callable;
this.result = result;
}
#Override
public void run() {
callable.setDataAfterLoading(result);
}
}
}
it works, but I cannot figure how I could replicate correctly the behaviour of publishProgress() and onProgressUpdate() of AsyncTask useful to show actual progress rather that just indeterminate
I cannot give the same code as yours but hopefully you get the idea.
Everything is self explain in code itself.
import android.app.*;
import android.graphics.*;
import android.os.*;
import android.widget.*;
import java.lang.ref.*;
public class MainActivity extends Activity
{
private static final class HeavyJob implements Runnable
{
private final WeakReference<Handler> handler;
private final Thread thread;
private boolean isAlive;
private boolean state;
private int progress;
public final HeavyJob(final Handler handler)
{
this.handler = new WeakReference<Handler>(handler);
thread = new Thread(this);
isAlive = true;
thread.setPriority(Thread.NORM_PRIORITY);
thread.start();
}
#Override
public final void run()
{
while(isAlive) {
try {
synchronized(this) {
while(!state) this.wait();
}
Thread.sleep(200L); //Let say this a heavy job which takes 200 m/s each round.
progress += 10;
final Handler hanRef = handler.get();
if(hanRef == null) {
isAlive = false;
handler.clear();
break;
}
final Message msg = Message.obtain();
msg.what = 0;
msg.arg1 = progress;
hanRef.sendMessageAtTime(msg, SystemClock.uptimeMillis()); //Update its progress each round.
} catch(final InterruptedException e) {}
}
//Finished ???
final Handler hanRef = handler.get();
if(hanRef != null) {
final Message msg = Message.obtain();
msg.what = 1;
msg.arg1 = progress; //Make your progress is 100% completed and updated.
//msg.obj = bitmap;
hanRef.sendMessageAtTime(msg, SystemClock.uptimeMillis());
}
}
public final synchronized void resume()
{
if(isAlive) {
state = true;
this.notify();
}
}
public final void suspend()
{
state = false;
thread.interrupt();
}
public final void stop()
{
isAlive = false; // In case interrupt() does nothing (Thread was not in sleep nor wait mode).
thread.interrupt();
handler.clear();
}
}
private static final class UIHandler extends Handler
{
private final WeakReference<MainActivity> activity;
public final UIHandler(final MainActivity activity)
{
super(Looper.getMainLooper());
this.activity = new WeakReference<MainActivity>(activity);
}
#Override
public final void handleMessage(final Message msg)
{
final MainActivity referent = activity.get();
if(referent != null) {
switch(msg.what) {
case 0: referent.onProgress(msg.arg1); break;
case 1: referent.onPostExecute(msg.arg1, (Bitmap)msg.obj); break;
}
}
}
}
private ProgressBar pb;
private ImageView iv;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
pb = findViewById(R.id.pb);
iv = findViewById(R.id.next);
UIHandler handler = new UIHandler(this);
//Initilize the object but will not run yet.
HeavyJob hj = new HeavyJob(handler);
//Run the job
hj.resume();
//Pause the job
hj.suspend();
//Resume the job
hj.resume();
//Stop the job
hj.stop();
//Multiple jobs
for(int i=0; i<10; i++) {
new HeavyJob(handler);
}
}
public final void onProgress(final int progress) {
pb.setProgress(progress);
}
public final void onPostExecute(final int progress, Bitmap bitmap)
{
pb.setProgress(progress);
if(bitmap != null) iv.setImageBitmap(bitmap);
}
}
The best I founded to do it is:
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
Button btn_start;
TextView text;
ProgressBar progressBar1, progressBar2;
int num = 0;
ExecutorService service;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = findViewById(R.id.textHello);
btn_start = findViewById(R.id.btn_start);
progressBar1 = findViewById(R.id.progressbar1);
progressBar2 = findViewById(R.id.progressBar2);
btn_start.setOnClickListener(v -> toDo());
}
private void toDo() {
service = Executors.newSingleThreadExecutor();
service.execute(() -> {
runOnUiThread(() -> {
// onPreExecute method of AsyncTask
progressBar1.setVisibility(View.VISIBLE);
progressBar2.setVisibility(View.VISIBLE);
});
// doInBackground of AsyncTask
for (int i = 1; i <= 10000; i++) {
num = i;
runOnUiThread(() -> {
// onProgressUpdate method of AsyncTask
progressUpdate(num);
});
}
runOnUiThread(() -> {
// onPostExecute method of AsyncTask
progressBar1.setVisibility(View.GONE);
progressBar2.setVisibility(View.GONE);
});
});
}
public void progressUpdate(Integer i) {
text.setText(String.valueOf(i));
progressBar2.setProgress(i);
}
}
Related
I have several classes:
WorkerQueue - queue.
WorkerRunnable - runnable which contain worker thread method with loop and its stop flag.
SomeClass - just class for initalize and management thread stuff.
I need to stop thread in destroy method. For it I create endTask method but it doesn't work thread still waiting.
If I replace called endTask method to mWorkerQueue.add(...), it's working fine and thread stop waiting.
class WorkerQueue {
private Queue<BasePrintTask> mTaskQueue = new ArrayDeque<>();
private Object obj = new Object();
public BasePrintTask get() {
while (mTaskQueue.isEmpty())
{
try {
synchronized (obj) {
obj.wait();
}
}
catch (InterruptedException ie)
{
ie.printStackTrace();
}
}
return mTaskQueue.remove();
}
public void add(BasePrintTask task) {
mTaskQueue.add(task);
synchronized (obj) {
obj.notify();
}
}
public void endTask() {
synchronized (obj) {
obj.notify();
}
}
}
public class WorkerRunnable implements Runnable
{
private boolean mFlag = false;
private WorkerQueue mWorkerQueue = null;
public WorkerRunnable(WorkerQueue workerQueue)
{
this.mPrinterManager = printerManager;
this.mWorkerQueue = workerQueue;
}
public void endThread() { mFlag = false; }
public void startThread() { mFlag = true; }
#Override
public void run() {
while(mFlag)
{
try
{
BasePrintTask task = mWorkerQueue.get();
}
catch (NoSuchElementException el)
{
}
}
}
}
public class SomeClass
{
private final Context mContext;
private WorkerQueue mWorkerQueue = new WorkerQueue();
private Thread mWorkerThread = null;
private WorkerRunnable mWorkerRunnable = null;
public SomeClass(final Context context)
{
this.mContext = context;
}
public void init()
{
mWorkerRunnable = new WorkerRunnable(mWorkerQueue);
}
public int prepare() {
mWorkerRunnable.startThread();
mWorkerThread = new Thread(mWorkerRunnable);
mWorkerThread.start();
return 0;
}
public int destroy() {
mWorkerRunnable.endThread();
mWorkerQueue.endTask();
try {
if(mWorkerThread.isAlive()) {
mWorkerThread.join();
}
}
catch (InterruptedException e) {
}
return 0;
}
How to use Saved State module for ViewModel in Background Thread
For MutableLiveData we have the option to use setvalue and postvalue , where Postvalue can be used in background thread.
How ever How can we use BACKGROUND THREAD FOR Saved State module for ViewModel
here Is the code I am trying
public class CommonViewModel extends ViewModel {
private SavedStateHandle mState;
public CommonViewModel(SavedStateHandle savedStateHandle) {
mState = savedStateHandle;
}
private static final String NAME_KEY = "name";
private Executor mExecutor = Executors.newSingleThreadExecutor();
public LiveData<ArrayList<CommonOwn>> getCart() {
if (mState.getLiveData(NAME_KEY) == null) {
initCart();
}
return mState.getLiveData(NAME_KEY);
}
public void initCart() {
mState.set(NAME_KEY, new ArrayList<CommonOwn>());
}
public void addItemToCart(CommonOwn commonOwn) {
if (getCart().getValue() == null) {
initCart();
}
ArrayList<CommonOwn> cartItemList = new ArrayList<CommonOwn>(getCart().getValue());
if (cartItemList.contains(commonOwn)) {
int a = cartItemList.indexOf(commonOwn);
cartItemList.remove(a);
} else {
cartItemList.add(commonOwn);
}
// mState.set(NAME_KEY, cartItemList);
mExecutor.execute(new Runnable() {
#Override
public void run() {
mState.set(NAME_KEY, cartItemList);
}
});
}
}
when using background thread The following error occurs
java.lang.IllegalStateException: Cannot invoke setValue on a background thread
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:487)
at androidx.lifecycle.LiveData.setValue(LiveData.java:306)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at androidx.lifecycle.SavedStateHandle$SavingStateLiveData.setValue(SavedStateHandle.java:367)
at androidx.lifecycle.SavedStateHandle.set(SavedStateHandle.java:256)
at com.example.CommonViewModel$1.run(CommonViewModel.java:63)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
how can we solve this issue.
The following line can never be null:
mState.getLiveData(NAME_KEY) == null
Hope this illustrates the solution. You just rely on the MutableLiveData from SavedStateHandle:
public class CommonViewModel extends ViewModel {
private MutableLiveData<ArrayList<CommonOwn>> cart;
public CommonViewModel(SavedStateHandle savedStateHandle) {
cart = savedStateHandle.getLiveData(NAME_KEY);
}
private static final String NAME_KEY = "name";
private Executor mExecutor = Executors.newSingleThreadExecutor();
public MutableLiveData<ArrayList<CommonOwn>> getCart() {
return cart;
}
public void addItemToCart(CommonOwn commonOwn) {
ArrayList<CommonOwn> cartItemList;
if(cart.getValue() == null) {
cartItemList = new ArrayList<CommonOwn>();
} else {
cartItemList = cart.getValue();
}
if (cartItemList.contains(commonOwn)) {
int a = cartItemList.indexOf(commonOwn);
cartItemList.remove(a);
} else {
cartItemList.add(commonOwn);
}
// mState.set(NAME_KEY, cartItemList);
mExecutor.execute(new Runnable() {
#Override
public void run() {
cart.postValue(cartItemList);
}
});
}
}
I have a problem with a simple android application. It has a SurfaceView with simple drawing, but activity orientation sometimes seems not to be changed after a screen rotation.
This is how an activity looks in the portrait mode:
landscape mode:
but sometimes, when I rotate the screen, an activity looks like this in the portrait mode:
MainActivity.java:
package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback
{
private Thread mGameThread;
private GameApp mGameApp;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SurfaceView mainView = (SurfaceView)findViewById(R.id.mainView);
SurfaceHolder holder = mainView.getHolder();
holder.addCallback(this);
mGameApp = new GameApp(getResources(), holder);
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
mGameThread = new Thread(new Runnable()
{
#Override
public void run()
{
mGameApp.run();
}
});
mGameThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
mGameApp.setSurfaceSize(width, height);
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
mGameApp.setRunning(false);
while (retry)
{
try
{
mGameThread.join();
retry = false;
}
catch (InterruptedException e)
{
}
}
}
}
GameApp.java:
package com.example.myapplication;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.view.SurfaceHolder;
public class GameApp
{
private Resources mResources;
private SurfaceHolder mSurfaceHolder;
private int mCanvasHeight = 1;
private int mCanvasWidth = 1;
private volatile boolean mRun = false;
public GameApp(Resources resources, SurfaceHolder surfaceHolder)
{
mResources = resources;
mSurfaceHolder = surfaceHolder;
}
public void setSurfaceSize(int width, int height)
{
synchronized (mSurfaceHolder)
{
mCanvasWidth = width;
mCanvasHeight = height;
}
}
public void run()
{
setRunning(true);
while (mRun)
{
Canvas canvas = null;
try
{
canvas = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder)
{
if (mRun && canvas != null)
{
draw(canvas);
}
}
}
finally
{
if (canvas != null)
{
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
public void setRunning(boolean b)
{
mRun = b;
}
private void draw(Canvas canvas)
{
canvas.drawColor(Color.GREEN);
Drawable cellImage = mResources.getDrawable(R.drawable.cell);
final float cellWidth = mCanvasWidth / 6;
final float cellHeight = mCanvasHeight / 6;
for (int i = 0; i < 6; i++)
{
for (int j = 0; j < 6; j++)
{
float x = i * cellWidth;
float y = j * cellHeight;
cellImage.setBounds(Math.round(x), Math.round(y), Math.round(x + cellWidth), Math.round(y + cellHeight));
cellImage.draw(canvas);
}
}
}
}
Eventual answer:
The problem is that you are doing a while loop in your GameApp's thread that locks the canvas and then unlocks without any long blocking or sleep in between.
The SurfaceHolder#lockCanvas documentation states:
If null is not returned, this function internally holds a lock until the corresponding unlockCanvasAndPost(Canvas) call, preventing SurfaceView from creating, destroying, or modifying the surface while it is being drawn.
So this means that the destroy code which is run from main thread needs to run between the unlockCanvasAndPost and the next lockCanvas. But since you have no sleep or even other code in between (except for the while condintion check), the chance of this happening is very small, and - depending on the device - could practically take forever.
To fix this, put a sleep in your game app to achieve the wanted framerate,
in it's most simple from this could look like this.
class GameApp
...
while (mRun)
{
Canvas canvas = null;
try
{
canvas = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder)
{
if (mRun && canvas != null)
{
draw(canvas);
}
}
}
finally
{
if (canvas != null)
{
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
// Add some sleep time depending on how fast you want to refresh
try {
Thread.sleep(1000/60); //~60 fps
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Original answer 1: What could have helped in case of handling orientation changes
Seems like the surface is not always re-drawn on a config change.
Try overriding the onConfigurationChanged method of the activity and then triggering a re-layout of the surface
in MainActivity.java:
...
#Override
public void onConfigurationChanged(Configuration newConfig) {
// You should save (SurfaceView)findViewById(R.id.mainView) in a field for better performance, but I'm putting it here for shorter code.
SurfaceView mainView = (SurfaceView)findViewById(R.id.mainView);
mainView.invalidate();
mainView.requestLayout();
}
...
More info on these methods nicely explained here:
Usage of forceLayout(), requestLayout() and invalidate()
Original answer 2: A way to check if your threads are blocked
Another possibility is that you have a thread lock issue on your main thread.
For checking that you could change your activity like this:
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
private Thread mGameThread;
private GameApp mGameApp;
private static View currMainView;
private static Thread logPosterThread = new Thread(new Runnable() {
volatile boolean mainLogDone = true;
#Override
public void run() {
Runnable mainThreadLogger = new Runnable() {
#Override
public void run() {
Log.d("THREAD_CHECK", "Main Thread is ok");
mainLogDone = true;
}
};
while (true) {
try {
int sleepTime = 1000;
Thread.sleep(sleepTime);
if (currMainView != null) {
if (mainLogDone) {
mainLogDone = false;
Log.d("THREAD_CHECK", "Main Thread doing post");
currMainView.post(mainThreadLogger);
} else {
Log.w("THREAD_CHECK", "Main Thread did not run the runnable within " + sleepTime + "ms");
mainLogDone = true;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
static {
logPosterThread.start();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final SurfaceView mainView = (SurfaceView) findViewById(R.id.mainView);
currMainView = mainView;
SurfaceHolder holder = mainView.getHolder();
holder.addCallback(this);
mGameApp = new GameApp(getResources(), holder);
}
#Override
public void onPause() {
...
And then see if those logs still get printed with 1000ms in between when the issue happens.
When you try this, you will see that actually the main thread is hanging when this happens.
Android handler question
I created a separate thread file
Refer to the thread in another activity.
There is a problem when trying to change UI with handler.
This part is nullpoint.
Message hdmsg= handler.obtainMessage();
I do not know which part is the problem.
ClientThread.java
public class ClientThread extends Thread{
public ClientThread() {
}
public void run(){
try{
Thread currThread = Thread.currentThread();
while (currThread == thisThread) {
String recvData = ct_in.readUTF();
StringTokenizer st = new StringTokenizer(recvData, SEPARATOR);
int command = Integer.parseInt(st.nextToken());
switch (command) {
case MDY_WAITINFO: {
StringTokenizer st1 = new StringTokenizer(st.nextToken(), DELIMETER);
StringTokenizer st2 = new StringTokenizer(st.nextToken(), DELIMETER);
/*
code~
*/
Message hdmsg= handler.obtainMessage();
hdmsg.obj=st;
handler.sendMessage(hdmsg);
break;
}
}
}
} catch (IOException e) {
System.out.println(e);
release();
}
}
}
RoomList.java
public class HostRoomListActivity extends AppCompatActivity {
public static Handler handler;
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SocketHostRoom client = new SocketHostRoom();
client.start();
handler = new Handler(){
public void handleMessage(Message msg){
String txtmsg = msg.obj.toString();
}
};
}
class SocketHostRoom extends Thread{
public void run(){
ClientThread thread = new ClientThread();
thread.start();
thread.requestHostRoomList();
}
}
}
}
Try calling HostRoomListActivity.handler.obtainMessage(); instead of justhandler.obtainMessage();
Sorry for the confusion it still needs to be called this way but also you are calling SocketHostRoom client = new SocketHostRoom(); before you are setting the handler
handler = new Handler(){
public void handleMessage(Message msg){
String txtmsg = msg.obj.toString();
}
};
Try setting the handler first the reason this is the issue is because when you call public static Handler handler; it will set it as null
I'm writing a Android Whack-a-mole game. As we all know, the moles will pop up frequently on the UI and the timer text view is changed every second. So I used handlers for all of these works. I was told to use handlers instead of java.util.Timer in this question.
However, It didn't work the way I wanted it to. I mean it kind of worked but the timer text view's text changed irrationally. When I checked the logcat it said I'm doing too much work on the main thread.
Back then, when I was developing winforms programs, this problem never occurs. I was thinking about putting those handlers on another thread but as I know only the UI thread can access UI stuff, right?
Here is my countdown class:
package com.whackanandroid;
import android.os.Handler;
import android.widget.TextView;
public class CountDown {
private Handler handler;
private int timeLeft;
private TextView textView;
private boolean paused;
private CountDownListener listener;
private Runnable countDownTask = new Runnable () {
#Override
public void run() {
if (!paused) {
timeLeft--;
displayTime ();
handler.postDelayed (this, 100);
if (timeLeft == 0) {
pauseCountDown ();
if (listener != null)
listener.onCountDownFinished ();
}
}
}
};
private void displayTime () {
textView.setText (Integer.toString (timeLeft / 10));
}
public void pauseCountDown () {
paused = true;
}
public void startCountDown () {
paused = false;
handler.post (countDownTask);
}
public void setCountDownListener (CountDownListener listener) {
this.listener = listener;
}
public CountDown (TextView tv) {
textView = tv;
timeLeft = 600;
handler = new Handler ();
}
}
And here is my phone class (the "mole")
package com.whackanandroid;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import java.util.Random;
public class Phone {
private ImageView image;
private Handler handler;
private Random random;
private State state;
private boolean paused;
private Runnable appearTask = new Runnable () {
#Override
public void run() {
if (!paused) {
if (random.nextInt (3) < 2) {
setState (State.ANDROID);
Game.getInstance ().addScore (10);
} else {
setState (State.APPLE);
Game.getInstance ().addScore (-5);
}
handler.postDelayed (disapperTask, random.nextInt (1000) + 700);
}
}
};
private Runnable disapperTask = new Runnable () {
#Override
public void run() {
if (!paused) {
setState (State.NONE);
handler.postDelayed (appearTask, random.nextInt (2000) + 1000);
}
}
};
private View.OnClickListener imageOnClick = new View.OnClickListener () {
#Override
public void onClick(View v) {
//TODO add OnClickListener
}
};
public void setState (State value) {
state = value;
switch (state) {
case NONE:
image.setImageResource (R.drawable.phone);
break;
case ANDROID:
image.setImageResource (R.drawable.androidphone);
break;
case APPLE:
image.setImageResource (R.drawable.applephone);
break;
}
}
public State getState () {
return state;
}
public void stopTimers () {
paused = true;
}
public void startTimers () {
paused = false;
if (getState () == State.NONE) {
handler.postDelayed (appearTask, random.nextInt (2000) + 1000);
} else {
handler.postDelayed (disapperTask, random.nextInt (1000) + 700);
}
}
public Phone (ImageView view, Random r) {
view.setOnClickListener (imageOnClick);
image = view;
random = r;
handler = new Handler ();
image.setImageResource (R.drawable.phone);
}
}
I think my code just looks horrible. Hope you don't mind. If you need any more code, please tell me at once.