I have an Android App that has a class which runs a thread. Basically the same as here.
The thread at the moment updates a text-view with a calculated value every 500 ms and additionally logs the value, so I can see it in adb-logcat.
When I exit my Application with the back-button of the device, the thread still runs in the background. (Which is what I want). The adb-logcat still gives me the values, that the thread is calculating.
But when I reopen the application, the textview is not updated anymore!
What do I have to do, that it resumes updating the textview, when I open the app again?
Here is my simplified code:
SensorProcessor.java
public class SensorProcessor implements Runnable {
protected Context mContext;
protected Activity mActivity;
private volatile boolean running = true;
//Tag for Logging
private final String LOG_TAG = SensorProcessor.class.getSimpleName();
public SensorProcessor(Context mContext, Activity mActivity){
this.mContext = mContext;
this.mActivity = mActivity;
}
public void run() {
while (running){
try {
final String raw = getSensorValue();
mActivity.runOnUiThread(new Runnable() {
public void run() {
final TextView textfield_sensor_value;
textfield_sensor_value = (TextView) mActivity.findViewById(R.id.text_sensor);
textfield_sensor_value.setText("Sensor Value: " + raw); // <-------- Does not update the field, when app resumes
Log.v(LOG_TAG, raw); // <-------- Still working after the app resumes
}
});
Thread.sleep(100);
} catch (InterruptedException e) {
//When an interrupt is called, we set running to false, so the thread can exit nicely
running = false;
}
}
Log.v(LOG_TAG, "Sensor Thread finished");
}
}
MainActivity.java
public class MainActivity extends Activity implements OnClickListener, OnInitListener {
//Start the Thread, when the button is clicked
public void onClick(View v) {
if (v.getId() == R.id.button_start) {
runnable = new SensorProcessor(this.getApplicationContext(),this);
thread = new Thread(runnable);
thread.start();
}
}
You can extend Application class and insert there getter and setter method to your Runnable. Here is example of MyApplication (don't forget to add the manifest connection!), in manifest.xml:
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppBaseTheme" >
Then MyApplication:
public class MyApplication extends Application {
private SensorProcessor mSensorProcessor = null;
public SensorProcessor getCurrentSensorProcessor(){
return mSensorProcessor;
}
public void setSensorProcessor(SensorProcessor mSensorProcessor){
this.mSensorProcessor = mSensorProcessor;
}
}
Inside onCreate() of your activity by call :
((MyApplication)getApplication()).getCurrentSensorProcessor().mActivity = this;
you need also to modify the Runnable constructor:
public SensorProcessor(Context mContext, Activity mActivity){
this.mContext = mContext;
this.mActivity = mActivity;
((MyApplication)mActivity.getApplication()).setSensorProcessor(this);
}
And don't forget to empty the instance of mSensorProcessor by calling this inside the Runnable when finish :
((MyApplication)mActivity.getApplication()).setSensorProcessor(null);
Finally you need to modify onClick in your Activity:
if (v.getId() == R.id.button_start) {
SensorProcessor mSensorProcessor = ((MyApplication)getApplication()).getCurrentSensorProcessor();
if (mSensorProcessor != null)
mSensorProcessor.mActivity = this;
else {
runnable = new SensorProcessor(this.getApplicationContext(), this);
thread = new Thread(runnable);
thread.start();
}
}
It should work, maybe by some minor changes.
Related
MyThread class is used to change the value of the myValue attribute in ActivityTwo Class.
public class MyThread implements Runnable {
private ActivityTwo activity;
public MyThread(ActivityTwo activity) {
this.activity = activity;
}
#Override
public void run() {
while(true){
activity.setValue(2);
}
}
}
ActivityTwo is an AppCompatActivity activity which runs as the main Thread.
public class ActivityTwo extends AppCompatActivity {
private MyThread myThread;
private Button startLogBtn;
private int myValue;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_two);
startLogBtn = findViewById(R.id.startLogBtn);
myThread = new MyThread(this);
startLogBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
new Thread(myThread).start();
}
});
}
public void setValue(int value){
this.myValue= value;
}
}
When I Click the startLogButton it goes to a white screen and then restart the app. What should I do? I have no idea what has gone wrong.
Thanks in advance.
The issue was I have violated the rule "Do not access the Android UI toolkit from outside the UI thread".
ref : https://developer.android.com/guide/components/processes-and-threads
As a solution I found that I can use thread communication using message passing.
ref : Communication between UI thread and other threads using handler
I start a Thread in a Fragment and use an Interface call, to #Override a method in the Fragment starting the thread. This #Override stops a ProgressDialog and changes the Text of a TextView in the Fragment.
When I do the same in an Activity, there is no Problem but now when using a Fragment I got the "only Thread that creates a View can touch it's views" - Error. So I used getActivity().runOnUiThread(runnable) and posted the code to the MainThread, but why do I need to do this, since it works in a Activity without this? Did I made a mistake?
The Thread
//interface
private ConnectToDevice connectToDevice;
//C-Tor
public Thread_ConnectToDevice(BluetoothDevice device, ConnectToDevice connectToDevice ) {
this.mBluetoothDevice = device;
this.connectToDevice = connectToDevice;
}
//call
connectToDevice.connectionSuccess(false, null);
Fragment
//make Thread
thread_connectToDevice = new Thread_ConnectToDevice(mBluetoothDevice, Fragment_RoutineStartConnection_setPassword.this);
thread_connectToDevice.start();
//CallBack
//Thread Connect Success
#Override
public void connectionSuccess(final Boolean bSuccess,final BluetoothSocket mSocket) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
if(bSuccess){
mProgessDialog.setTitle(R.string.tv_Fragmentsetpassword_Connected_CheckPW);
if(thread_connectedToDevice != null){
if(thread_connectedToDevice.isAlive()){
thread_connectedToDevice.interrupt();
}
}
thread_connectedToDevice = new Thread_ConnectedToDevice(mSocket, sTryingDonglePassword);
thread_connectedToDevice.start();
}else{
mProgessDialog.dismiss();
tv_Fragmentsetpassword_userhint
.setTextColor(getResources().getColor(R.color.Mercedes_RED, null));
tv_Fragmentsetpassword_userhint.setText(R.string.tv_Fragmentsetpassword_ConnectionFailed);
}
}
});
}
I have the feeling that I passed the wrong listener Instance to the Thread.
As asked this is the callback realized the same way but in a Activity:
Thread
//listener
private Finished_AskingForInformation listener;
//C-Tor
public Td_AskForInformation(
Finished_AskingForInformation listener) {
this.listener = listener;
}
//call
listener.AskingFinished();
Activity
//Create and start thread
td_askForInformation = new Td_AskForInformation(this);
td_askForInformation.start();
//CallBack
#Override
public void AskingFinished() {
mProgressDialog.dismiss();
}
I have tried so many ways of solving my problem, but still no success.I have a method, which returns me a string value and I am using it to update TextView on my screen like this:
outCPU.setText(getCpuInfo());
Which would be fine, but I need to update this TextView until back button was pressed.
I guess i have need a while loop which starts after activity has been created and stops after back button was pressed. This loop should be in a new thread, because:- I have to load the activity first and execute the loop in another thread so the executing won't affect main thread and loading of the activity.
As I've already said, I don't know how to do this properly even though i have spent few hours on it.
Could someone show me an example how to get this done? Thanks...!!
EDITED - WORKING:
private Handler mHandler;
private int i;
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_systeminfo);
outCPU = (TextView) findViewById(R.id.outCPU);
outMEM = (TextView) findViewById(R.id.outMEM);
outTASKS = (TextView) findViewById(R.id.outTASKS);
i = 0;
mHandler = new Handler();
mHandler.post(mUpdate);
}
private Runnable mUpdate = new Runnable() {
#Override
public void run() {
outCPU.setText(getCpuInfo());
outMEM.setText(getMemInfo());
outTASKS.setText(getTasksInfo());
i++;
mHandler.postDelayed(this, 1000);
}
};
#Override
public void onBackPressed() {
mHandler.removeCallbacks(mUpdate);
super.onBackPressed();
Log.i("MSG", "Going back");
finish();
}
You can use AsyncTask to perform operations on UI Thread while being in a Thread. Or you can use 'my favorite' , the combination of Thread and Handler. To make sure the thread is stopped when back is pressed, you can use handler.removeCallBacks(Runnable) The following example could solve your problem:
//Global
Handler h = new Handler();
private static boolean flag = true;
public void updateTextView(){
// call thread here
h.post(thread);
}
// take this thread out side so that it can be stopped with handler
Thread thread = new Thread(){
public void run(){
while(flag)
outCPU.setText(getCpuInfo());
}
}
public void onBackPressed(){
flag = false;
h.removeCallBacks(thread);
super.onBackPressed();
}
Use a shared flag somewhere in your app:
private volatile boolean wasPressed = false;
In while loop, check this flag:
while (!wasPressed) {
runOnUiThread(new Runnable() {
#Override
public void run() {
outCPU.setText(getCpuInfo());
}
});
// sleep for a while
}
On button click listener, switch wasPressed to true.
The question is how to communicate with an Android phone to a server, so that if the Activity is left and the call in the Activity was not successful to repeat the transaction once again automatically. Just now I use the AsyncTask of Android to communicate with the server:
new AsyncTask<String, Void, List<String>>() {
#Override
protected void onPreExecute(
showWaitDialog();
}
#Override
protected void onPostExecute(List<String> msgList) {
//here I put the handling after the POST ie. error and success handling
hideWaitDialog();
if (msgList.isEmpty() {
//success handling --> starting an new Activity
} else {
errorView.setText (...);
errorLayout.setVisibility (View.VISIBLE);
}
}
#Override
protected List<String> doInBackground(String... params) {
List<String> msgs = new ArrayList<String>();
try{
//for example submitting an JSONObject
JSONObject result = HttpUtils.sendHttpPost(
AppConstants.WEB_URL, jsonObject);
//error handling on the result
boolean hasErrors = JsonResult.isOk(result);
if (hasErrors) {
// adding errors to msgs list
String[] errorMessages = JsonResult.getErrorMessages (result,...);
fillList (msgs, errorMessages);
return msgs;
}
} catch (CommunicationError er) {
msgs.add (er...);
}
return msgs;
}
}
The problem with this approach is, that if I don't have a successful transmission of the data I must stay in the same Activity. Until now I show an error message to the user and he is in charge to submit by a button again the results to the server.
What I'm looking for is some Activity that remains persistent in the memory which runs later in the case that the transmission wasn't made.
As an application case I use this to dynamically upload pictures for a Waypoint in a map if I pressed that waypoint. In some case it can happens that the connection to the mobile service provider isn't available (mountains, forest, far apart from antenna). Then I want to leave the map Activity and switch to the detail view of this waypoint. In the success case I put the picture into my model classes and make an serialization. If the user clicks again on the same waypoint the picture is not loaded again. In the non success case I don't want to wait that the user clicks against on the waypoint to retrieve the image. In fact I need a background task, some sort of a queue that pictures of waypoints that are already visited on couldn't be retrieved are loaded until the communication part gives back a positive result and the image can be written into the model. The next time the user is pressing the Waypoint the picture will be then present.
Are there any best practices for making such a code implementation?
Is there any example around?
Is there a better way of doing this?
Yes, you need to Implement Intent Service for this requirement
According to the developers website
The IntentService class provides a straightforward structure for running an operation on a single background thread.
For complete details and working source code, Go through the Android Docs
Thanks to the answer of David.
I just read after the suggestion the tutorial at
[1] http://code.tutsplus.com/tutorials/android-fundamentals-intentservice-basics--mobile-6183
After my tests I prefered a Service (not an IntentService)
and created a service: SubmissionService
public class SubmissionIntentService extends Service {
private List<PendingMessage> pMsgList = new CopyOnWriteArrayList<PendingMessage>();
private Handler handler = new Handler();
private boolean hasAppStopped = false;
private Runnable runner;
public SubmissionIntentService() {
super();
Log.d (TAG, "Service created...");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
PendingMessage pMessage = (PendingMessage) intent.getParcelableExtra(AppConstants.MESSAGE_OBJECT);
synchronized (pMsgList) {
pMsgList.add(pMessage);
}
if (runner == null) {
handler.postDelayed(runner = initializeRunnable(), 500);
}
return Service.START_NOT_STICKY;
}
private void runAsLongAppIsActive (Runnable runner) {
if (!hasAppStopped) {
handler.postDelayed (runner, SOME_INTERVAL_CONSTANT);
}
}
private Runnable initializeRunnable() {
Runnable result;
result = new Runnable() {
#Override
public void run() {
if (pMsgList.isEmpty()) {
runAsLongAppIsActive (this);
return;
}
PendingMessage[] pMArray = null;
synchronized(pMsgList) {
pMArray = pMsgList.toArray (new PendingMessage[pMsgList.size()]);
}
if (pMArray==null || pMArray.length==0) {
runAsLongAppIsActive (this);
return;
}
Log.d (TAG, "Message List size is actually :"+pMArray.length);
for (PendingMessage pM: pMArray) {
try {
JSONObject jsonMess = JSONSendMessage.buildOutput (pM);
JSONObject result = HttupUtils.sendHttpPost (WEB_URL, jsonMess);
boolean hasErrors = JSONResult.isOk (result);
if (hasErrors) {
//TODO: error handling in case of transmission
//don't remove the message from the queue
runAsLongAppIsActive(this);
return;
}
//remove pending transmission of the queue if success
synchronized (pMsgList) {
pMsgList.remove (pM);
}
//inform over receiver if activity is shown
Intent broadcastIntent = new Intent();
//put data in intent
sendBroadcast (intent);
//more important
WayPointModel model = ModelInstance.getWayPointModel();
model.addToModel (pM, result);
model.store();
} catch (Exception e) {
continue; //try to send other messages
}
}
runAsLongAppIsActive (this);
}
};
return result;
}
}
#Override
public void onDestroy() {
hasAppStopped = true;
handler.removeCallbacks (runner);
super.onDestroy();
}
}
Further I added a ResponseReceiver:
public class ResponseReceiver extends BroadcastReceiver {
public static final String ACTION_RESP = "MESSAGE_PROCESSED";
#Override
public void onReceive(Context context, Intent intent) {
//work in progress...
}
}
and in the Activity where I want to be informed about events:
public class SomeActivity extends Activity {
private ResponseReceiver receiver;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
IntentFilter filter = new IntentFilter(ResponseReceiver.ACTION_RESP);
filter.addCategory(Intent.CATEGORY_DEFAULT);
receiver = new ResponseReceiver();
registerReceiver(receiver, filter);
...
}
}
and finally to send messages over Http:
Intent msgIntent = new Intent(this, SubmissionIntentService.class);
msgIntent.putExtra(...);
startService(msgIntent);
don't forget to declare the service in your manifest:
<service android:name="ch.xxx.app.service.SubmissionIntentService" />
Observations:
- I called the method startService(...) from different Activities. The constructor is only called once.
==> I have just on instance of the service for all Activities (exactly what I need).
What I don't get until now:
- Putting back data to the Activity. What is if the Activity is at the moment no shown?
In the mainactivity I have create a handler as instance variable:
SeekBar seek_bar;
MediaPlayer player;
Handler seekHandler = new Handler();
Then I have the following two methods in the MainActivity to update the seekbar as the audio plays in media player:
public void getInit() {
seek_bar = (SeekBar) findViewById(R.id.seek_bar);
}
Runnable run = new Runnable() {
#Override
public void run() {
seekUpdation(); **//Exception comes here while closing the app**
}
};
public void seekUpdation() {
seek_bar.setProgress(mMediaPlayer.getCurrentPosition());
seekHandler.postDelayed(run, 1000);
}
The problem I am facing is that when the audio is running and user closes the application using device back buttton. I get NullPointerException. The destroy method of activity is:
#Override
protected void onDestroy() {
super.onDestroy();
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
}
Remove any queued messages/callbacks from the handler in your onDestroy. My bet is that its running the last message after onDestroy is called.