I'm trying perform some steps inside a function on Android. I would like to tell to user what is happen in a specific moment without exit of my function. something like it:
public boolean updateServiceList() {
LinearLayout start = (LinearLayout) findViewById(R.id.start);
LinearLayout major = (LinearLayout) findViewById(R.id.major);
TextView messenger = (TextView) findViewById(R.id.messenger);
Integer i = 0;
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
boolean isOnline = cm.getActiveNetworkInfo().isConnectedOrConnecting();
if(isOnline==false) {
Button exit = (Button) findViewById(R.id.exit_btn);
ProgressBar pg = (ProgressBar) findViewById(R.id.progress);
exit.setText("OK");
exit.setVisibility(View.VISIBLE);
exit.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
gMain.this.finish();
}
});
pg.setVisibility(View.GONE);
messenger.setText(R.string.nointernet);
while(isOnline==false) {
i++;
messenger.setText(messenger.getText()+"Try: "+ i.toString);
boolean isOnline = cm.getActiveNetworkInfo().isConnectedOrConnecting();
}
return false;
} else {
isOnline = cm.getActiveNetworkInfo().isConnected();
while (isOnline==false || 1==1) {
isOnline = cm.getActiveNetworkInfo().isConnected();
}
}
return true;
}
My problem is in "while(isOnline)" where I can't see the update messages. I tried invalidate() and postInvalidate() there but no results. Any ideas?
EDIT:
I found the solution! There it is:
How to refresh a TextView while looping in Android?
The Rackers's answer:
public class myClass extends Activity{
private Handler mHandler;
private TextView text;
private int i;
#Override
public void onCreate(Bundle savedInstanceState) {
text = (TextView) findViewById(R.id.textView01);
i = 0;
mHandler = new Handler();
mHandler.post(mUpdate);
}
private Runnable mUpdate = new Runnable() {
public void run() {
text.setText("My number: " + i);
i++;
mHandler.postDelayed(this, 1000);
}
};}
So, at my case I just call the others steps if the current is solved. If not, I just post the step again...
Your approach will not work.
When you call messenger.setText(), you think the screen is supposed to update right then. You are mistaken.
What really happens when you call messenger.setText() is that a message goes on a message queue, to be processed by the main application thread. That's the same thread you're tying up with your while() loop. While you are in that while() loop, your entire UI is frozen: no clicks, no updates, nothing.
I suggest that you get rid of the while() loop and move along with the rest of your app.
Related
I'm new to using Android Studio, and am currently writing an app that makes use of sensors in the phone to control another device. I searched and haven't found much that helps me address my issue. Eventually I need to transmit the data I get from the sensors over a network, but I'm nowhere near that point yet so that's for another day.
Right now, the app can access the sensors and display it on the phone screen. Each time the on screen button is pressed it updates the readings. What I want it to do though is once I press the button once, continually update the values in real time as the phone is moved around. If the button is pressed again I'd like it to stop. Below is my current code in the main activity. What I tried to do was use an integer that I would toggle each time the button was pressed and run a do while loop on one of the values. When the do while loop is in there it won't do anything. If I take it out, it runs like it originally did updating the values each time the button is pressed. I also am displaying the value of "toggle" next to the sensor values and it is toggling when the do while loop isn't in there. I don't understand why the do while loop won't run at all. I also tried using a boolean value and toggling between true and false but I got the same result. I also realize that the way the do while loop is set up it would probably not be able to be stopped, but I would've thought it would've at least entered the loop and kept running which would've at least got me started.
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.widget.TextView;
import android.widget.Button;
import android.view.View;
public class MainActivity extends AppCompatActivity
{
MySensorUpdateThread mySensorUpdateThread = null;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mySensorUpdateThread = new MySensorUpdateThread(this);
SensorManager sensorManager = (SensorManager) this.getSystemService(SENSOR_SERVICE);
final float[] mValuesMagnet = new float[3];
final float[] mValuesAccel = new float[3];
final float[] mValuesOrientation = new float[3];
final float[] mRotationMatrix = new float[9];
final Button btn_valider = (Button) findViewById(R.id.btn1);
final TextView txt1 = (TextView) findViewById(R.id.textView1);
final SensorEventListener mEventListener = new SensorEventListener() {
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
public void onSensorChanged(SensorEvent event) {
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
System.arraycopy(event.values, 0, mValuesAccel, 0, 3);
break;
case Sensor.TYPE_MAGNETIC_FIELD:
System.arraycopy(event.values, 0, mValuesMagnet, 0, 3);
break;
}
}
;
};
setListners(sensorManager, mEventListener);
btn_valider.setOnClickListener(new View.OnClickListener()
{
public void onClick(View view)
{
mySensorUpdateThread.toggleThread();
if (mySensorUpdateThread.isRunning())
{
mySensorUpdateThread.start();
}
}
});
}
public void setListners(SensorManager sensorManager, SensorEventListener mEventListener)
{
sensorManager.registerListener(mEventListener, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL);
sensorManager.registerListener(mEventListener, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
SensorManager.SENSOR_DELAY_NORMAL);
}
public class MySensorUpdateThread extends Thread
{
private boolean keepRunning = false;
private String sensorResults = "";
private MainActivity mActivity;
public MySensorUpdateThread(MainActivity activity)
{
this.mActivity = activity;
}
public void toggleThread()
{
this.keepRunning = !this.keepRunning;
}
public boolean isRunning()
{
return this.keepRunning;
}
public String getSensorResults()
{
return this.sensorResults;
}
#Override
public void run()
{
int i = 0;
int maxIterations = 100;
try{
while(this.keepRunning)
{
// This keeps the thread from going on too long in case
if(i > maxIterations)
{
this.keepRunning = false;
break;
}
// This causes the thread to rest for 50ms to
// slow things down
try
{
Thread.sleep(50);
}
catch(InterruptedException e)
{
}
SensorManager.getRotationMatrix(mRotationMatrix, null, mValuesAccel, mValuesMagnet);
SensorManager.getOrientation(mRotationMatrix, mValuesOrientation);
sensorResults = "Roll/Pitch (degrees): " + /*mValuesOrientation[0]*(180/Math.PI) + " "+ "," + " " +*/
mValuesOrientation[1] * (180 / Math.PI) + " " + "/" + " " +
mValuesOrientation[2] * (-180 / Math.PI);
// Now post the results to the UI Thread
runOnUiThread(new Runnable(){
#Override
public void run(){
txt1.setText(getSensorResults());
}
});
}
}
catch()
{
Log.e(TAG, ex.getMessage());
}
}
}
}
I found it helpful when I used handler thread for long time tasks, handler thread is androids official way for handling long running task. Here is the offical link
for more information on how to use a handler thread.
https://developer.android.com/reference/android/os/HandlerThread.html
Put your sensor reading in a background thread. This is just an example. Make modifications as may be needed!
In your Activity class you will need to setup your background thread object:
MySensorUpdateThread mySensorUpdateThread = null;
In your Activities onCreate() method initialize the background thread by adding:
mySensorUpdateThread = new MySensorUpdateThread();
In your onClick() method start and stop you background thread:
public void onClick(View view){
mySensorUpdateThread.toggleThread();
if(mySensorUpdateThread.isRunning()){
mySensorUpdateThread.start();
}
});
Now here is the code for the background thread:
public class MySensorUpdateThread extends Thread{
SensorManager sensorManager = null;
private boolean keepRunning = false;
private String sensorResults = "";
public void toggleThread(){
this.keepRunning = !this.keepRunning;
}
public void isRunning(){
return this.keepRunning;
}
public String getSensorResults(){
return this.sensorResults;
}
#Override
public void run(){
if(sensorManager == null){
sensorManager = (SensorManager) this.getSystemService(SENSOR_SERVICE);
}
int i = 0;
int maxIterations = 100;
String mess = "";
try{
while(this.keepRunning){
mess = "Number of iterations: " + i;
Log.e(TAG, mess);
// This keeps the thread from going on too long in case
if(i > maxIterations){
this.keepRunning = false;
break;
}
// This causes your thread to rest for 500ms it might be a
// good idea to slow things down a bit -- just a suggestion
Thread.sleep(500);
// Get your sensor data here and set it to the
// local variable to be posted to the UI Thread
//this.sensorResults = sensorString;
// Now post the results to the UI Thread
runOnUiThread(new Runnable(){
#Override
public void run(){
txt1.setText(getSensorResults());
}
}
}
}
catch(){
Log.e(TAG, ex.getMessage());
}
}
}
You will probably want to stop the background thread on the Activities onPause() method and perhaps restart it on onResume().
I have the following code, which I was hoping would count up to 3 million on the screen.
It compiles and runs, displaying 3,000,000 at the end on the emulator. My question is how do I force a redraw / display of the textbox during the loop please?
/** Called when the activity has become visible. */
#Override
protected void onResume() {
super.onResume();
Log.d(msg, "The onResume() event");
TextView textbox1=(TextView)findViewById(R.id.TextView1);
for(double l=0; l<=3000000; l++){
textbox1.setText("" + l);
}
}
The view is only disabled after onResume has finished running.
You may want to set the text in the textview, update some state in the activity (like an int field), and register some code to run after a while and increment. Look at using Handler, AsyncTask, or other options to defer code.
Here's a quick-and-dirty example with Handler.
final long DELAY_MILLIS = 50;
final Handler handler = new Handler();
int num = 0;
final Runnable runnable = new Runnable() {
public void run() {
if (num >= 3000000) return;
textbox1.setText("" + num);
num++;
// re-register ourself to run in DELAY_MILLIS;
handler.postDelayed(runnable, DELAY_MILLIS);
}
};
TextView textbox1;
protected void onResume() {
// more efficient to look this up once
this.textbox1 = (TextView)findViewById(R.id.TextView1);
runnable.run(); // will register itself to re-run
}
According to this answer the best way to show an incremental value in a TextView is to use ValueAnimator
public void animateTextView(int initialValue, int finalValue, final TextView textview) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(initialValue, finalValue);
valueAnimator.setDuration(1500);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
textview.setText(valueAnimator.getAnimatedValue().toString());
}
});
valueAnimator.start();
}
I have 2 buttons as well as a progressbar.
Now I want to count from 0 - 100 and update the progressbar with the counter. Unfortunately this count is so fast, that I will not see a progress (I can only see the progressbar jump from 0 to 100 within a 'millisecond'.).
This is what I tried so far, however the same thing happens as before.
I click the button to start the counter, the app becomes unresponsive (I calculate within the mainThread, so that is OKAY (and exactly what I want to accomplish here(!))) and shortly after sets the progressbar to 100. Nothing in between.
Now if you could head me into the correct direction, that'd be highly appreciated! :)
public class MainActivity extends ActionBarActivity {
public String TAG = "MainActivity";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addButtonListeners();
}
public void addButtonListeners() {
Button firstButton, secondButton;
final ProgressBar pgBar;
firstButton = (Button) findViewById(R.id.first_button);
secondButton = (Button) findViewById(R.id.second_button);
pgBar = (ProgressBar) findViewById(R.id.progressBar);
firstButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "First Button was pressed.");
for (int i = 0; i <= 100; i++) {
countProgBarUp(pgBar, i);
}
}
});
secondButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "Second Button was pressed.");
}
});
}
public void countProgBarUp(ProgressBar pgBar, int counter) {
try {
Thread.sleep(100);
pgBar.setProgress(counter);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
...
}
The UI becomes unresponsive because you are doing this on the main thread and sleeping. If you do not return from the onClick() then the main thread cannot handle UI changes, such as displaying your new progress. Only the main thread can update UI components and draw them. It is often called the UI thread because of this.
You could try creating a Runnable which updates the progress bar to the new count. Create a Handler which is bound to the UI thread and call postDelayed() for the Handler and give it an instance of the Runnable. Then in the Runnable just post it again as long as you haven't reached you max progress.
So I have a music application. I am trying to update the UI with the progress of the media player (like current time, current song, album cover) everytime the song changes. I found that using interfaces was a awesome magical way of communication between activity and fragments so I implemented an interface in my MusicManger class. My code will show what and how did it.
Two problems
1) Commented look below, ExecutorService seems to stop after one loop. No Errors in catch block (this is why I tagged with java)
2) Commented please look, All the System.out methods print but the UI doesn't update. I do believe I called the method from mainThread so it should update.
I'll show code in logical order will add titles in bold before code segment to tell you basic idea of code.
Passing UI references from fragment to MusicManager class, code below in Fragment class
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_item_songlist, container, false);
// Set the adapter
TextView musicManagerSongName, musicManagerCurrent, musicManagerTotal;
ProgressBar musicManagerProgress;
ImageView musicManagerImageView;
mListView = (AbsListView) view.findViewById(R.id.slist);
musicManagerSongName = (TextView)view.findViewById(R.id.textView12);
musicManagerCurrent = (TextView)view.findViewById(R.id.textView10);
musicManagerTotal = (TextView)view.findViewById(R.id.textView11);
musicManagerProgress = (ProgressBar)view.findViewById(R.id.progressBar);
musicManagerImageView = (ImageView)view.findViewById(R.id.imageView2);
MainActivity.mediaPlayer.passUIReferences(musicManagerSongName, musicManagerCurrent, musicManagerTotal, musicManagerProgress, musicManagerImageView, view);
// line above is a method within MusicManager that takes the references will show code next!
ImageButton playbutton = (ImageButton)view.findViewById(R.id.playbuttonbar);
ImageButton nextButton = (ImageButton)view.findViewById(R.id.nextbuttonbar);
ImageButton backButton = (ImageButton)view.findViewById(R.id.backbuttonbar);
ImageButton toggleButton = (ImageButton)view.findViewById(R.id.shufflebuttonbar);
ImageButton pausebutton = (ImageButton)view.findViewById(R.id.pausebuttonbar);
playbutton.setBackgroundResource(R.drawable.playbuttonbar);
playbutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
MainActivity.mediaPlayer.stateChange(1);
}catch(Exception e) {
}
}
});
backButton.setBackgroundResource(R.drawable.backbutton1);
nextButton.setBackgroundResource(R.drawable.nextbutton1);
toggleButton.setBackgroundResource(R.drawable.shufflebuttonselected);
pausebutton.setBackgroundResource(R.drawable.pausebutton1);
pausebutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
try {
MainActivity.mediaPlayer.stateChange(0);
} catch (Exception e){
}
}
});
mListView.setAdapter(mAdapter);
((MainActivity) mListener).restoreActionBar();
return view;
}
As Commended above the code that is located in MusicManager class that takes references and stores them. Also shows interface implementation with MusicManager class. And the Executor service
public void passUIReferences(View... views) {
this.uiElements = views;
}
private ExecutorService executorService = Executors.newSingleThreadExecutor();
private MediaplayerUpdateInterface uiUpdateInterface;
public MediaPlayerManager(MediaplayerUpdateInterface inter) {
this.player = new MediaPlayer();
this.uiUpdateInterface = inter;
// The below line starts the single thread while loop for excutorservice and only loops and prints "this" once after I start one song then it never loops again
executorService.submit(new Runnable() {
#Override
public void run() {
while(true) {
if (player.isPlaying() && uiElements != null) {
System.out.println("this");
uiUpdateInterface.updateUI(uiElements, 0);
}
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
public interface MediaplayerUpdateInterface {
public void updateUI(View[] views, int type);
}
Finally some code from MainActivity class that actually is suppose to update the UI note that both println's work as expected but only once as stated above because of the executorservice issue
public static MediaPlayerManager mediaPlayer = new MediaPlayerManager(new MediaPlayerManager.MediaplayerUpdateInterface() {
#Override
public void updateUI(View[] views, int type) {
System.out.println("check1 " + type);
updateMediaplayerViews(views, type);
}
});
private static void updateMediaplayerViews(View[] views, int type)
{
switch(type) {
case 0:
System.out.println("that?");
((TextView)views[0]).setText(mediaPlayer.getCurrentSongInfo().getName().length() > 22? mediaPlayer.getCurrentSongInfo().getName().substring(0, 19)+"..." : mediaPlayer.getCurrentSongInfo().getName());
break;
}
views[views.length - 1].invalidate();
}
The view array is shown perviously! Also the last view in the array is shown as the main view for songlist fragment.
I am sorry for all the code I've tried to debug it as you can see from my println's there is just something I am unaware of going on here.
Ok so there was an error that I needed to catch to see within the following code:
private static void updateMediaplayerViews(View[] views, int type)
{
switch(type) {
case 0:
System.out.println("that?");
((TextView)views[0]).setText(mediaPlayer.getCurrentSongInfo().getName().length() > 22? mediaPlayer.getCurrentSongInfo().getName().substring(0, 19)+"..." : mediaPlayer.getCurrentSongInfo().getName());
break;
}
views[views.length - 1].invalidate();
}
The issue is I was trying to change the view from a different thread then the one which created it. Solving it was pretty long and painful but basically I made it nonstactic used more interfaces then used the famous
Mainactivity.runOnUiThread(new Runnable(....));
i need to check a variable for change, if the change happens i will update a textview.
so i created a new thread with a while loop for this purpose. i check the variable every 1 second, via Thread.sleep()
this thread created and started in onCreate(). so it's one time.
the problem is every time i flip my phone (from vertical to horizontal or ...) a new thread will be created.
here is my code:
public class HomeActivity extends Activity
{
private final static int LOAD_SUCCESSFULL = 1;
private final long LOCATION_THREAD_SLEEP = 1000;
private boolean flag = false;
static TextView lat;
static TextView lon;
static Location currentLocation = null;
Thread locationThread;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.new_home2);
this.getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.new_home_titlebar);
lat = (TextView)findViewById(R.id.t2rt3);
lon = (TextView)findViewById(R.id.t2rt4);
/* FindLocation class is a helper class that find location via simcard or gps in separate thread,
same problem happen with this thread also, it create multiple thread, for ease of work i
commented this part.
*/
//FindLocation fn = new FindLocation(this);
locationThread = new Thread(null, loadLocation, "loadLocationHomePage");
locationUpdater();
}
private static Handler locationUpdateHandler = new Handler()
{
public void handleMessage(Message msg)
{
switch(msg.what)
{
case LOAD_SUCCESSFULL:
lat.setText(Double.toString(currentLocation.getLatitude()));
lon.setText(Double.toString(currentLocation.getLongitude()));
//stopThread();
break;
}
}
};
private Runnable loadLocation = new Runnable()
{
public void run()
{
//boolean flag = false;
while(!flag)
{
if(Data.currLocation != null)
{
currentLocation = new Location(Data.currLocation);
Message msg = locationUpdateHandler.obtainMessage(LOAD_SUCCESSFULL);
locationUpdateHandler.sendMessage(msg);
//return;
flag = true;
//return;
}
else
{
try
{
Thread.sleep(LOCATION_THREAD_SLEEP);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
};
public void locationUpdater()
{
//Thread locationThread = new Thread(null, loadLocation, "loadLocationHomePage");
locationThread.start();
}
so how i can solve this?
Actually the problem is that EveryTime you flip the phone a new instance of Activity is created and because of this you on every rotation you get a call on onCreate() where you are blindly creating a new Thread and Starting the new Thread.
This is the default behavior of every Activity but we can change this re-creation of Activity by stating an attribute in AndroidManifest file for the Activity
<activity
android:name="yourPackage.ActivityName"
android:configChanges="keyboardHidden|orientation|screenSize"
</activity>
This will prevent from creation of Activity on orientation change.
You will also get these orientation event if you override
#Override
public void onConfigurationChanged(Configuration newConfig) {}
Hope this will solve this problem without implementing such a complex logic which may broke in some other uses case.
I think you aren't perhaps going about this in the most efficient way possible.
But if your question is simply, how do i prevent multiple worker threads from being spawned, you should look into a UIless fragment.
http://www.vogella.com/articles/AndroidFragments/article.html#headlessfragments1
i don't know why android doing this. if i putted my code in onResume(), then this behavior make sence but in my case, i don't know.
anyway i found a dirty solution. the idea is finding list of all thread and search them for mine, if it existed prevent to create another.
public boolean checkThreadExist()
{
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
Thread[] threadArray = threadSet.toArray(new Thread[threadSet.size()]);
for(int i = 0; i < threadArray.length ; i++)
{
if(threadArray[i].getName().equalsIgnoreCase("loadLocationHomePage"))
return true;
}
return false;
}
updated onCreate() :
if(checkThreadExist())
{
}
else
{
locationThread = new Thread(null, loadLocation, "loadLocationHomePage");
locationUpdater();
}