Accessing UI Thread or Main Thread from External class implementing Runnable - java

I hate The application may be doing too much work on its main thread, skipped XXX frames.. warning, Also it degrades the users UI interaction experience. So trying it with proper way as android wants it to be...
MainActivity :
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener
{
public Button StartBg;
private static final String TAG = "TASK_FIRST";
private Handler mainHandler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
StartBg = findViewById(R.id.StartBg);
StartBg.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view)
{
ExampleRunner ExampleRunnerObj = new ExampleRunner(50000);
new Thread(ExampleRunnerObj).start();
}
});
}
}
When i keeps below class as inner class of MainActivity, It is able to access UI Components.
ExampleRunner :
public class ExampleRunner implements Runnable
{
int count;
public ExampleRunner(int count)
{
this.count = count;
}
#Override
public void run()
{
Handler threadHandler = new Handler(Looper.getMainLooper());
for(int i = 0; i < count; i++)
{
Log.d(TAG,"PERFORMING : "+i+"\n");
if(i == 25000)
{
threadHandler.post(new Runnable()
{
#Override
public void run()
{
// StartBg.setText("50k");
// OR RETURN SOMETHING..
}
});
}
}
}
}
But when i makes ExampleRunner as separate external class, it says StartBg can not be resolved...
So, How should I :
Make external java class which implements Runnable, Access Main Threads UI components...?
Or At least return something to mainActivity where i am starting it, so that from mainActivity i can access it?
Thanks in advance.

You need to pass the external class the Button as a reference:
public class ExampleRunner implements Runnable
{
int count;
Button startBg;
public ExampleRunner(int count, Button startBg) {
this.count = count;
this.startBg = startBg;
}
and create it with it:
ExampleRunner ExampleRunnerObj = new ExampleRunner(50000, StartBg);
then it will be able to use it in run().
Right now, the ExampleRunner is an anonymous class accessing the StargBg variable declared locally.

If, I right understood you, for your solution, AsyncTask it is good solution.
Check docs https://developer.android.com/reference/android/os/AsyncTask

Related

Running CountDownTimer inside AsyncTask throws java.lang.RuntimeException - Looper.prepare()

I have a .lrc file and I need to go over every line with a CountDownTimer. I have tried using AsyncTask to do so but I get the error:
Caused by: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
On line new CountDownTimer... I tried doing it with runnable but I still get the same error. My goal is to get it to go over every line in .lrc file which looks like this:
[00:04.15]Help me, it's like the walls are caving in
[00:10.46]Sometimes I feel like giving up
[00:13.63]But I just can't
...
I am not sure how efficient it is to do it the way I am trying to do. I am thinking of going through every line in the doInBackground(). If there is a better way to do it then let me know. But to begin with, why am I getting the EXCEPTION ?
Just to note.. I have simplified the code as much as I could so it would be easier to understand what I am trying to do.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyView myView = new
myView.play();
}
}
public class MyView{
public void play() {
new CustomAsync().execute();
}
}
class CustomAsync extends AsyncTask<Lyric, Void, Void> {
protected Void doInBackground(Lyric... param) {
startLyricCountDownTimer(param);
return null;
}
protected void onPostExecute(Void param) {
//Print Toast or open dialog
}
private void startLyricCountDownTimer(Lyric lyric){
new CountDownTimer(30000, 10) { //This is where it throws the error
public void onTick(long millisUntilFinished) {
//Do the thing
}
public void onFinish() {
}
}.start();
}
}
EDIT
Is it better to go with the AsyncTask and do like Son Truong suggested or to use the following code for each and every lrc line?
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
new CountDownTimer(millisInFuture,countDownInterval) {
#Override
public void onTick(
CountDownTimer uses a Handler to post messages to a Message Queue of a Thread which has a Looper. onTick and onFinish will be called on which thread based on where you create CountDownTimer instance.
In your case because you create CountDownTimer instance in doInBackground method of AsyncTask so these two methods will be call on AsyncTask thread.
In constructor of CountDownTimer, it will create Handler instance as well. The Handler will check whether or not current thread has a Looper, if not it will throw a RuntimeException with message.
Can't create handler inside thread that has not called
Looper.prepare()
Because AsyncTask uses a thread which has no Looper, that why your app crashes.
My suggestion is in doInBackground method you open a connection to .lrc file and read each line, for each line read, use runOnUIThread to send the line to UI thread (then you can process the line read there by display a Toast on screen, etc).
Update: I will demo how to read line by line from a file then display it on a text view each 3 seconds.
First write a class which read from an inputstream line by line
static class ReadLyricTask extends AsyncTask<InputStream, String, Void> {
WeakReference<MainActivity> mMainActivity;
ReadLyricTask(MainActivity activity) {
mMainActivity = new WeakReference<>(activity);
}
#Override
protected Void doInBackground(InputStream... inputStreams) {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStreams[0]));
String line;
try {
while ((line = reader.readLine()) != null) {
publishProgress(line);
}
} catch (IOException e) {
// Do nothing.
} finally {
try {
inputStreams[0].close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
MainActivity activity = mMainActivity.get();
if (activity != null) {
activity.displayLyricLineOnTextView(values[0]);
}
}
}
Then just use it in MainActivity
public class MainActivity extends AppCompatActivity {
private static final int UPDATE_LYRIC_TEXT_INTERVAL = 3000; // Change lyric text each 3 seconds.
private int mCurrentInterval = 0;
private TextView mLyricTextView;
private Handler mHandler = new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mLyricTextView = findViewById(R.id.lyricText);
// I put a file named lyric.lrc in raw folder, for your case just open an input stream from a file.
InputStream inputStream = getResources().openRawResource(R.raw.lyric);
new ReadLyricTask(this).execute(inputStream);
}
private void displayLyricLineOnTextView(final String lyricLine) {
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
mLyricTextView.setText(lyricLine);
}
}, mCurrentInterval);
mCurrentInterval += UPDATE_LYRIC_TEXT_INTERVAL;
}
}
CountDownTimer runs in separate thread and no need of asynctask to run a timer.Best solution would be create a service and make service to trigger a timer.
As timer run on non ui thread, while updating ui make sure you update from UI thread.You could use UI handler or runOnUithread method to update view.

Thread freezes the UI when using a while loop inside Run() method of Runnable object

I'm trying to start a thread to perform a piece of code inside a while loop. The problem is that the UI hangs and the progress dialog stops immediately after being shown. when removing the handler object it runs fine, but no changes happen to the UI of course. What is the problem?
MainActivity class
public class MainActivity extends AppCompatActivity {
...
Handler mHandler = new Handler(Looper.getMainLooper()) {
#Override
public void handleMessage(Message msg) {
mProgressDialog.dismiss();
mTextOutput.setText(mRepeatedText);
}
};
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
mProgressDialog = new ProgressDialog(MainActivity.this);
mProgressDialog.setTitle("Text Repeater");
mProgressDialog.setMessage("We're generating your text...");
mProgressDialog.show();
Runnable mRunnable = new Runnable() {
#Override
public void run() {
mRepeatedText = StringUtils.repeat(mText, mNumberOfIterations);
mHandler.sendEmptyMessage(0);
}
};
mThread = new Thread(mRunnable);
mThread.start();
StringUtils class
public class StringUtils {
public static String repeat(String text, int numberOfIterations){
StringBuilder buffer = new StringBuilder(text.length()*numberOfIterations);
while(numberOfIterations-- > 0){
buffer.append(text);
}
return buffer.toString();
}
}
It looks like the return statement in the method repeat in the StringUtils class is not in the right place. This is probably what you are looking for:
public class StringUtils {
public static String repeat(String text, int numberOfIterations) {
StringBuilder buffer = new StringBuilder(text.length()*numberOfIterations);
while(numberOfIterations-- > 0){
buffer.append(text);
}
return buffer.toString();
}
}
The problem was in this line of code:
mTextOutput.setText(mRepeatedText);
When using a huge number of iterations, for example a 100,000 times. This makes a 100,000 copy of my entered word, sentence or whatever. And it takes time to load all that in the textview.

How to interact with UI from a different class

I would like to update my UI from a different class. I am familiar with runOnUiThread() method, but don't know how to implement it in this scenario?
public class UploadAct extends MainActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upload);
}
//my code and functions would go here
}
Then, my UploadData class
public class UploadData extends UploadAct {
public void doSomethig(){
printThis("I want to print this message to the UI");
}
public void printThis(String messsage) {
final String mess = message;
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(),mess,Toast.LENGTH_LONG).show();
// I want this to display on the main thread
txt_upload.setText(mess);// and this also
}
});
}
}
Use BroadcastReceiver
// define a Broadcast Intent Action in String resources
<string name="broadcast_id">MY_BROADCAST_ID</string>
// register receiver in constructor/onCreate()
MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();
IntentFilter myIntentFilter = new IntentFilter();
myIntentFilter.addAction(context.getString(R.string.broadcast_id));
context.registerReceiver(myBroadcastReceiver, myIntentFilter);
// place your BroadcastReceiver in MainActivity, your UploadData class
public class MyBroadcastReceiver extends BroadcastReceiver {
public MyBroadcastReceiver(){
super();
}
#Override public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Broadcast received");
if(intent.getAction() != null && intent.getAction().equals(context.getString(R.string.broadcast_id)) ){
// do something
}
}
}
// send Broadcasts from where you want to act, your UploadAct class.
Intent intent = new Intent();
intent.setAction(context.getString(R.string.broadcast_id));
context.sendBroadcast(intent);
Log.d(TAG, "Broadcast sent.");
// you can unregister this receiver in onDestroy() method
context.unregisterReceiver(myBroadcastReceiver);
You can also use an interface to update your UI as a listener.
First, Create an interface
public interface UpdateTextListener {
void updateText(String data);
}
Then, Call its method in your UploadData class
public class UploadData extends UploadAct {
UpdateTextListener listener;
public void doSomethig(){
listener.updateText("data to be loaded");
}
}
Then, Update your UploadAct by listening to this method
public class UploadAct extends MainActivity implements UpdateTextListener {
#Override
public void updateText(String data) {
textview.setText(data);
}
}
First of all - there is no such thing like UI of some class. There are activities that can have handles to UI widgets (ex TextView). If you want to make some changes to UI from your UploadData class you have to pass somehow reference to this class. Possibly by constructor:
public class UploadData extends UploadAct{
private TextView txt_upload;
public UploadData(TextView tv)
{
txt_upload = tv;
}
public void doSomethig(){
printThis("I want to print this message to the UI")
}
public void printThis(String messsage) {
final String mess = message;
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getApplicationContext(),mess,Toast.LENGTH_LONG).show();// I want this to display on the main thread
txt_upload.setText(mess);// and this also
}
});
}
}
I assume that you create DataUpload in your MainActivity.
Everyone use so much library to be trendy as they forget built in functions in Android :)
For sure isn't any hard thing to use AsyncTask, beside it provides the doInBackground function it has the https://developer.android.com/reference/android/os/AsyncTask.html#publishProgress(Progress...) function too, what you have asked for.
Just create a class (UploadTask) which extends AsyncTask and override 1-2 function.

Android communication between main thread and other thread

I want to change dynamically the text of a textview, but I will need the same logic if I want to make a game thread, so I need to make the communication between the main one and the second one.
I have the files :
MainActivity
public class MainActivity extends ActionBarActivity {
public static Handler mHandler;
Runnable thread = new SampleThread();
TextView txt1 = (TextView) findViewById(R.id.txt1);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar actionBar = getSupportActionBar();
actionBar.hide();
//hiding status bar
if (Build.VERSION.SDK_INT < 16) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}
setContentView(R.layout.activity_main);
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
// i want to change the text of txt1 here
}
};
new Thread(thread).start();
}
}
SampleThread
package com.example.katsar0v.myapplication;
import android.util.Log;
/**
* Created by Katsar0v on 1/21/2015.
*/
public class SampleThread implements Runnable {
#Override
public void run() {
int two = 0;
while(two<10) {
two++;
try {
Thread.sleep(1000);
//instead of logging, i want to send the text to main UI
Log.d("MSG", String.valueOf(two + "sec"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
The problem I see is, how do I change the text with the handler, when my thread is in another file? Or should I make the second class static within the first one (and what should I do when the code gets really long, it can't be all in one file)?
You could implement a custom Interface in order to handle it from your main activity.
On your SampleThread:
public interface TextViewChangeListener
{
public void onTextViewChanged(String newName);
}
TextViewChangeListener mListener;
Then call mListener.onTextViewChanged(String newName) wherever you want to have the new name in your TextView. Remember to initialize mListener with an instance of your MainActivity first, otherwise you will get a null pointer exception. You can do that either in the constructor of SampleThread or by creating a method for the purpose.
In your activity you should implement SampleThread.TextViewChangeListener and override the onTextViewChanged.
#Override
public void onTextViewChanged(String newName)
{
//MyTextView.setText(newName);
}
Edit: untested code:
MainActivity:
public class MainActivity extends ActionBarActivity implements SampleThread.TextViewChangeListener {
#Override
public void onTextViewChanged(Message msg)
{
// process incoming messages here
// i want to change the text of txt1 here
}
public static Handler mHandler;
Runnable thread = new SampleThread(this);
TextView txt1 = (TextView) findViewById(R.id.txt1);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActionBar actionBar = getSupportActionBar();
actionBar.hide();
//hiding status bar
if (Build.VERSION.SDK_INT < 16) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else {
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}
setContentView(R.layout.activity_main);
new Thread(thread).start();
}
}
SampleThread:
package com.example.katsar0v.myapplication;
import android.util.Log;
/**
* Created by Katsar0v on 1/21/2015.
*/
public class SampleThread implements Runnable
{
public interface TextViewChangeListener
{
public void onTextViewChanged(Message msg);
}
public SampleThread(TextViewChangeListener mListener)
{
this.mListener = mListener;
}
TextViewChangeListener mListener;
#Override
public void run() {
int two = 0;
while(two<10) {
two++;
try {
Thread.sleep(1000);
mListener.onTextViewChanged(String.valueOf(two + "sec"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Let me know if that helped.
You can find some examples in Grafika, which does a lot of work off the UI thread. For example, TextureFromCameraActivity has a pair of handlers, one for the UI thread, one for the renderer thread. In onResume() you can see the main thread passing its handler to the renderer through a constructor, then retrieving the renderer thread's handler with a method call.
ContinuousCaptureActivity has a slightly different approach, using a Handler that also implements a callback interface. The handler object is passed to the CircularEncoder constructor as an interface instance. The public callback methods use the Handler internally.
The only tricky bit is if you're passing a Handler out of the non-UI thread. You either need to do it before the thread starts, or use appropriate thread synchronization operations to avoid data races.
You don't need to have your classes in the same file (and you really shouldn't unless one is nested inside the other). If they're in the same package then the default (package) scope will let them see each other. The first example from Grafika uses nested / private classes, the second example is more spread out.
Of course, if all you're trying to do is submit UI events from a non-UI thread, you can just use Activity.runOnUiThread().

Java runnable doesn't seem to be triggered in Android

I have a very simple test application I'm making, and to set the seekBar's position I'm using a runnable. Although I have very little experience with actually working with a runnable.
public class MySpotify extends Activity implements Runnable {
private SeekBar progress;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.spotify_app);
myProgress = (SeekBar) findViewById(R.id.myBar);
}
#Override
public void run() {
myProgress.setProgress(25);
}
}
If I move myProgress.setProgress(25); into the onCreate then it works. But I want it to be set off in the runnable. Any ideas?
You need to post() a Runnable to a Thread for it to execute. Try calling post(this); inside onCreate().
Try
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.spotify_app);
myProgress = (SeekBar) findViewById(R.id.myBar);
myProgress.post(new Runnable()
{
public void run()
{
myProgress.setProgress(25);
}
});
}
You need something to run the post() method on
You can start the run method by just calling run();
Be aware that it will execute on the main thread.
Also be aware that it will run only once since there is no loop.
if you want to update while doing something else you sjould create a new thread.
example:
public class MySpotify extends Activity{
private SeekBar myProgress; //I asume it is call "myProgress" instead of "progress"
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.spotify_app);
myProgress = (SeekBar) findViewById(R.id.myBar);
ThreadExample example = new ThreadExample();
example.start();
/* Start a new thread that executes the code in the thread by creating a new thread.
* If ou call example.run() it will execute on the mainthread so don't do that.
*/
}
private class ThreadExample extends Thread{
public void run() {
myProgress.setProgress(25);
}
}
}

Categories

Resources