I'm working on an editText and a recyclerView.
My recyclerView is updated when I write letters in my EditText.
I put a Timer inside my textWatcher in order to avoid sending requests each time user write a letter.
searchDestinationEt.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
//There is nothing to do here
}
#Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
if (timer != null) {
timer.cancel();
}
}
#Override
public void afterTextChanged(final Editable s) {
timer = new Timer();
//we schedule this in order to avoid sending useless request.
//We wait the user is finishing writing before sending requests
timer.schedule(new TimerTask() {
#Override
public void run() {
((Activity) context).runOnUiThread(new Runnable() {
#Override
public void run() {
actionsListener.onDestinationSearch(s.toString());
}
});
}
}, DELAY_SEND_REQUEST);
}
});
It works well but leakcanary says that I have a leak in this part of code.
Any idea ?
Sorry for being late with the response, but did you try separating textwatcher like this ?:
TextWatcher for more than one EditText
Why do you use Timer and TimerTask for delayed, not recurring action? The easiest and the common way is to use just a regular Handler with postDelayed():
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//do somthing here
}
}, DELAY_SEND_REQUEST);
The leak occurs because you're starting a thread which has a reference to your context (fragment or activity). And until your thread is done - it won't be garbage collected.
That means, for example, if a user types something and you're waiting for a time to start requesting and meanwhile the user turns the phone and orientation change occurs - your activity/fragment will be recreated - but the old one (which started a thread and should be used when thread is done) is not gone and still present in memory.
Why are doing a request on the UI Thread? It blocks the UI, you know that right? I assume an AsyncTask may fit better.
What should you do?
Replace Timer with Handler and do the requests in a worker thread. Regarding the leak you have 2 options:
a) do nothing since the time for which your activity/fragment will be preserved is very small and it will be GCed after the request is done. (not recommended)
b) Utilize the AsyncTask and in the constructor of the AsyncTask pass the context (your listener) and store it as a weak reference object, like this:
private static class SomeWorkTask extends AsyncTask<Void,Void,Void>{
private WeakReference<ActionsListenerWithContext> weakListener;
public SomeWorkTask(ActionsListenerWithContext listener){
this.weakListener = new WeakReference<>(listener);
}
#Override
protected Void doInBackground(Void... voids) {
//do some work here
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
if(weakListener.get() != null){
weakListener.get().callYourCallbacks();
}
}
}
and then you call it
new SomeWorkTask(listener).execute();
Utilizing WeakReference wrapper is a common and recommended practice.
Related
I am currently working on an app which uses Bluetooth, GPS and uploads data to a remote server. I have a simple button which launches a series of events and threads in order to let everything work together.
I am now adding TextView components on the screen, which show the user a more detailed process of what is happening. Is the GPS running? Is my Bluetooth device connected? Etc. This process can take up to 10 seconds, this is why I am adding some more information on what is happening on the background.
However, when I click my button, only the last change will be visible. I suppose the TextView components are rendered AFTER the Onclick?
An example:
#Override
public void onClick(View view) {
textView.setText(R.string.launching_text);
// start a thread
textView.setText(R.string.start_new_thread);
// start another thread
textView.setText(R.string.almost_there);
// start last thread
textView.setText(R.string.done);
}
Imagine this process taking about 10 seconds. It will look like the app "freezes", but the changes are not visible till after the OnClick finishes.
How can I show my information realtime, during the OnClick event? Is there perhaps a better practice? Is it possible to do some sort of way to asynchronously push TextView changes?
I’d suggest you first check this Android Performance Patterns video, to see some of the options at your disposal. I’d also advise to not perform multithreading in a lifecycle environment (e.g. Activities, Fragments) as this is just asking for trouble.
In your onClick example, R.string.done could (and most likely will) be displayed before the first thread has done its work. I’m assuming that’s not really what you want.
I have no knowledge of the problem you’re tackling, tools you’re using or the architecture you’re following, so here’s one slightly generic way to make it work. Each Thread in your onClick implementation comes with a status of sorts. You could represent this in code with a simple abstraction:
class Holder {
#StringRes int status;
Runnable runnable;
Holder(#StringRes int status, #NonNull Runnable runnable) {
this.status = status;
this.runnable = runnable;
}
}
Notice Runnable is used instead of Thread.
You’re also executing things in sequence. You could represent this in code with a simple List or a Queue, providing a fluid, expressive API, for example:
class StatusRunnableBuilder {
private final WeakReference<TextView> viewRef;
private final Queue<Holder> queue;
#StringRes private int finalStatus;
StatusRunnableBuilder(#NonNull TextView view) {
viewRef = new WeakReference<>(view);
queue = new ArrayDeque<>();
}
StatusRunnableBuilder addStep(#StringRes int status,
#NonNull Runnable runnable) {
queue.add(new Holder(status, runnable));
return this;
}
StatusRunnableBuilder withFinalStatus(#StringRes int status) {
finalStatus = status;
return this;
}
Runnable build() {
return new Runnable() {
#Override
public void run() {
for (Holder item: queue) {
updateStatus(item.status);
item.runnable.run();
}
if (finalStatus != 0) {
updateStatus(finalStatus);
}
}
};
}
private void updateStatus(#StringRes final int status) {
final TextView view = viewRef.get();
if (view != null) {
view.post(new Runnable() {
#Override
public void run() {
// As this has been posted to a queue,
// it could have been processed with some delay,
// so there is no guarantee the view is still present.
// Let's check again.
final TextView v = viewRef.get();
if (v != null) {
v.setText(status);
}
}
});
}
}
}
Then your onClick becomes something like:
#Override
public void onClick(View v) {
final Runnable runnable = new StatusRunnableBuilder(view)
.addStep(R.string.launching_text, launchingRunnable)
.addStep(R.string.almost_done, almostDoneRunnable)
.withFinalStatus(R.string.finally_done)
.build();
service.execute(runnable);
}
where service is an ExecutorService which allows you to create/shutdown on any lifecycle event, e.g.:
#Override
protected void onStart() {
super.onStart();
service = Executors.newSingleThreadExecutor();
}
#Override
protected void onStop() {
super.onStop();
service.shutdownNow();
}
You can use a Runnable with Handler. Handler will post updates to Runnable after certain intervals.
For Bluetooth connectivity you can go for Broadcast receivers as well.
You can try using an AsyncTask. It's pretty simple and it handles all the background threading as well as inter-thread communication for you. There are several considerations with it, to avoid memory leaks you can use EventBus or similar mechanics. Here's an article I found very useful:
http://simonvt.net/2014/04/17/asynctask-is-bad-and-you-should-feel-bad/
I'm using code that looks like this :
_thread = new Thread(){
#Override
public void run() {
try {
while (true) {
operate();
Thread.sleep(DELAY);
}
} catch (InterruptedException e) {
// Doesn't matters...
}
}
};
operate function looks like this :
// does things....
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
// adds an ImageView to the screen
}
});
// does other things...
At the bottom line, what i wanted to achieve is an operation that happens once in a while, without interrupting the main thread and the UI, something like a game-loop.
In the first 2 times that operate() runs, it adds the ImageView and everything is alright, but after 2 or 3 times it stops adding the ImageViews, but the UI is still running as usual. When i debugged the problem, i found out that after 3 times the run() method of the Runnable isn't called anymore, even thought the operate function was called.
The wired thing (for me) was that when i removed the Thread.sleep, everything worked fine (much faster of course...). I tried to replace it with a very long for loop (just for checking) and it worked, but of course it is not an appropriate solution to the problem.
I read about the problem, most of the people that asked this question did a thread.sleep or an infinite loop on the main thread, but, as i see it, i didn't do such thing. Many people wrote that you should replace the Thread.sleep with Handler.postDelayed. I tried to do it but it didn't work, maybe I did it wrong. I even tried replacing the runOnUiThread with other options I found on the internet, but all of them gave me the same exact results. I tried to replace the method that I'm adding the view to the activity, but all of them, again, gave the same result.
The waiting is crucial for this application. I got to find a way to wait sometime and then execute a function on the UI thread, cause this pattern returns at least a couple of times in my application.
It sounds like you want a post delay so that you can do the code on the UI thread after some delay. Handler Post Delay.
private static final int DELAY = 500;
private Handler mHandler;
private Runnable mRunnable;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private void start()
{
mHandler = new Handler();
mRunnable = new MyRunnable(this);
mHandler.postDelayed(mRunnable, DELAY);
}
private void stop()
{
mHandler.removeCallbacks(mRunnable);
}
private void doSomething()
{
// Do your stuff here.
// Reschedule.
mHandler.postDelayed(mRunnable, DELAY);
}
Recommended way of creating a Runnable.
private static class MyRunnable implements Runnable
{
private WeakReference<MainActivity> mRef;
// In here you can pass any object that you need.
MyRunnable(MainActivity activity)
{
mRef = new WeakReference<MainActivity>(activity);
}
#Override
public void run()
{
// Safety check to avoid leaking.
MainActivity activity = mRef.get();
if(activity == null)
{
return;
}
// Do something here.
activity.doSomething();
}
}
There could be several reasons why the UI Runnable isn't being executed. Probably the activity variable has something messed up with it or it's referencing the context incorrectly, or as you said the Thread.sleep() could be causing an issue. At this point more parts of the code needs to viewed to better solve the problem.
A better way of implementing your logic is to use a scheduled Timer instead of using an infinite loop with a Thread.sleep() in it. It will execute the code within a background thread. And then use a Handler to update the UI instead of activity.runOnUiThread(). Here's an example:
// Global variable within the activity
private Handler handler;
// Activity's onCreate()
#Override
protected void onCreate(Bundle savedInstanceState) {
handler = new Handler(getMainLooper());
Timer timer = new Timer("ScheduledTask");
// Timer must be started on the Main UI thread as such.
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
operate();
}
}, 0L, DELAY);
}
private void operate() {
// does things in background....
handler.post(new Runnable() {
#Override
public void run() {
// adds an ImageView to the screen from within the Main UI thread
}
});
// does other things in the background...
}
I have an auto complete feature which means every time the user types a letter, there is a network call to the server, the user can type very quickly and a lot
I usually use thirdparty libraries for network calls but this time they wouldn't work, I need to use AsyncTask by the looks of it.
I read that you can only execute an AsyncTask once. My question is, do I need to create an object of my AsyncTask everytime the user types a letter? is this the best approach?
I am aware of implementing Filterable in my adapter but I need to know about this without Filterable.
Instead of making network call when user type you can make a network call when user stops typing.
I was implement it for same case as yours.I have a AutocompleteextView and when user type i have to show him list of suggestions, but I call webservice when user stops typing.Below is my example -
private final static int DELAY_BEFOR_SEARCH = 2000;
Here is Autocomplete TextView Listener -
myAutocompleteTV.addTextChangedListener(new SearchListener ());
and here is Listener Class -
public class SearchListener implements TextWatcher{
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#Override
public void afterTextChanged(Editable s) {
try{
mMessageHandler.removeCallbacks(mSearchRunnable);
if(s.length()>0){
if(Utility.isNetworkAvailable(mContext)){
mMessageHandler.postDelayed(mSearchRunnable, DELAY_BEFOR_SEARCH);
}
else{
Toast.makeText(mContext, getResources().getString(R.string.no_network_message),Toast.LENGTH_SHORT).show();
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
and here is Runnable -
Runnable mSearchRunnable = new Runnable() {
public void run() {
//TODO call your webservice here
}
}
for example if user type want to search 'Android' and he types 'Andr' ans stops typing for 2000 ms then my network call execute.
In my activity oncreate method, i have called a service using OnStartCommand(). My requirement is when the user is on the same Activity (when the Activity is visible), a set of code should run repeatedly. (Example .. I should make a web service call and get the response and do some action based on it after regular intervals).
I have put this set of code in this method.
#Override
public int onStartCommand(Intent i, int flags , int startId){
// Code to be repeated
return Service.START_STICKY;
}
But, this is getting executed only once. How to make it run repeatedly from the time the user came to this page till he leaves this page ??
CountDownTimer.cancel() method seems to be not working.
I would recommend you to use Timer instead. It's much more flexible and can be cancelled at any time. It may be something like that:
public class MainActivity extends Activity {
TextView mTextField;
long elapsed;
final static long INTERVAL=1000;
final static long TIMEOUT=5000;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mTextField=(TextView)findViewById(R.id.textview1);
TimerTask task=new TimerTask(){
#Override
public void run() {
elapsed+=INTERVAL;
if(elapsed>=TIMEOUT){
this.cancel();
displayText("finished");
return;
}
//if(some other conditions)
// this.cancel();
displayText("seconds elapsed: " + elapsed / 1000);
}
};
Timer timer = new Timer();
timer.scheduleAtFixedRate(task, INTERVAL, INTERVAL);
}
private void displayText(final String text){
this.runOnUiThread(new Runnable(){
#Override
public void run() {
mTextField.setText(text);
}});
}
}
You can use Timer for the fixed-period execution of a method.
See here is a tutorial on this:
http://steve.odyfamily.com/?p=12
I'm trying to implement an auto-suggest like functionality for a part of my application. As a part of this, as a user types into a text field, I ping a web service for the results, parse the xml and update the listview with a list of returned results.
Here's the basic flow for my program. My question is if I'm stopping the thread the right way. Does calling thread.interrupt() suffice?
init() {
// <snip>
searchTxt.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable arg0) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
FetchResults();
}});
}
private void FetchResults()
{
if(mCurrentNetworkThread != null)
{
mCurrentNetworkThread.interrupt();
mCurrentNetworkThread = null;
}
mCurrentNetworkThread = new Thread(
new Runnable() {
public void run() {
List<NameValuePair> paramTable = new ArrayList<NameValuePair>(1);
paramTable.add(new BasicNameValuePair("searchText", searchTxt.getText().toString()));
String methodName = "GetSearchResults";
NetworkAccessClass nac = new NetworkAccessClass(paramTable, IP_ADDR, methodName, 0, this);
nac.startRequest();
}
});
mCurrentNetworkThread.start();
}
#Override
public void requestSucceeded(String responseMessage, int callID) {
parseResponseFromNetwork(responseMessage);
}
#Override
public void requestFailed(String responseCode, int callID) {
}
If that code is executed from an Activity you will most likely run into concurrency problems modifying something in the GUI thread from another thread without use of a Handler. The easy way around this i recommend using an AsyncTask. It solves any of the problems you will have in Android. These tasks can be canceled via the cancel() function and will be safe as far as any Threading exceptions in Android are concerned.
[Edit]
Make sure you check out Jon's comment below about using Loaders, a 3.0 function I wasn't aware of.
Since android 3.0, AsyncTasks will largely be replaced by Loaders
They can even be used down to Android 1.6 with the new compatibility library.
These links should help you along:
android-3-0-what-are-the-advantages-of-using-loadermanager-instances-exactly
Android Loader - Dev Guide
Try this Where to stop/destroy threads in Android Service class?