Anonymous class hold a reference to the enclosing class.
In the following example, I created a small Activity. In the onCreate method, I just add a timer on another Thread, add a CompositeDisposable and clear it in the onDestroy.
Obviously without the CompositeDisposable, it will create a memory leak. With the CompositeDisposable it doesn't create any memory leak but how is it even working ?
RxJava just interrupt the Thread and put null on every callback ? Can you provide some line that do this work in RxJava source code, i suppose it's somewhere near the dispose method.
public class MainActivity extends AppCompatActivity {
private String TAG = "MainActivity";
private CompositeDisposable composite = new CompositeDisposable();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
composite.add(Flowable
.just(1)
.timer(90, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.subscribeWith(new DisposableSubscriber<Long>() {
#Override
public void onNext(Long aLong) { sayHello(); }
#Override
public void onError(Throwable t) { sayHello(); }
#Override
public void onComplete() { sayHello(); }
}));
}
#Override
protected void onDestroy() {
super.onDestroy();
composite.clear();
}
public void sayHello () { Log.w(TAG, "Hello everyone"); }
It is precisely in the source of the dispose method. You can probably jump into the source of methods in your libraries within your IDE as well, in IntelliJ it's Ctrl+B on Windows or ⌘B on Mac, and in Eclipse it's F3.
Anyhow, here's the source of the dispose method (comments mine):
#Override
public void dispose() {
if (disposed) { // nothing to do
return;
}
OpenHashSet<Disposable> set; // this is the same type as our field that holds the Disposables
synchronized (this) {
if (disposed) {
return; // another thread did it while we got our lock, so nothing to do
}
disposed = true; // setting this flag is safe now, we're the only ones disposing
set = resources; // the references are now in this local variable
resources = null; // our field no longer has the references
}
dispose(set); // from here on out, only this method has the references to the Disposables
}
And then the complete code of the dispose(OpenHashSet<Disposable>) method that we called above on the last line (mostly just error handling which I believe is self-explainatory):
/**
* Dispose the contents of the OpenHashSet by suppressing non-fatal
* Throwables till the end.
* #param set the OpenHashSet to dispose elements of
*/
void dispose(OpenHashSet<Disposable> set) {
if (set == null) {
return;
}
List<Throwable> errors = null;
Object[] array = set.keys();
for (Object o : array) {
if (o instanceof Disposable) {
try {
((Disposable) o).dispose();
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
if (errors == null) {
errors = new ArrayList<Throwable>();
}
errors.add(ex);
}
}
}
if (errors != null) {
if (errors.size() == 1) {
throw ExceptionHelper.wrapOrThrow(errors.get(0));
}
throw new CompositeException(errors);
}
}
As you can see, at the end of that method, set can now be garbage collected, as nobody is holding a reference to it.
Related
After years, I'm trying to develop an Android app, using Firebase Firestore. I'm basically trying to replicate this Swift function:
func getCategories(onCompletion completionBlock: #escaping (_ categories: [Category]?, _ error: Error?) -> Void) {
firestore.collection("cats").getDocuments { (snap, error) in
guard let snap = snap else {
completionBlock(nil, error ?? anUnknownError)
return
}
var categories: [Category] = []
for document in snap.documents {
let cat = Category.init(data: document.data())
categories.append(cat)
}
completionBlock(categories, nil)
}
}
But I have no idea what is the equivalent of swift's blocks, even don't know if it exists.
I checked Firebase source codes. Query.get() returns Task<QuerySnapshot> so I tried to return a Task<List<Category>> without luck.
Any Help? Thank you.
EDIT: Android code added to clarify what I'm trying to do.
public class FirestoreService {
private static volatile FirestoreService singleton = new FirestoreService();
public static FirestoreService getInstance() {
return singleton;
}
private FirebaseFirestore firestore() {
// default firestore instance
FirebaseFirestore db = FirebaseFirestore.getInstance();
// default firestore settings
FirebaseFirestoreSettings settings = db.getFirestoreSettings();
// firestore settings builder
FirebaseFirestoreSettings.Builder builder = new FirebaseFirestoreSettings.Builder(settings);
// enable timstamps
builder.setTimestampsInSnapshotsEnabled(true);
// set new settings to db instance
db.setFirestoreSettings(builder.build());
// return db with new settings.
return db;
}
public void getProductCategories(Handler? handler) {
Task<QuerySnapshot> task = firestore().collection("coll").get();
task.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
try {
if (task.isSuccessful()) {
List<Category> cats = new ArrayList<>();
for (QueryDocumentSnapshot doc : task.getResult()) {
String id = doc.getId();
Map<String, Object> data = doc.getData();
Category cat = new Category(id, data);
cats.add(cat);
}
// now I need completion handler
} else {
Log.w("ERROR", "Error getting categories", task.getException());
}
} catch (Exception e) {
Log.e("ERROR", e.getMessage());
}
}
});
}
}
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FirestoreService.getInstance().getCategories().addCompletionListener(
// handle List<Category> and respresent in UI
);
}
}
Thank you very much four your help and lead #Daniel-b.
I've solved my issue now.
First I created an Interface for handling results; as you suggested.
public interface ResultHandler<T> {
void onSuccess(T data);
void onFailure(Exception e);
}
Then in the service class, I added ResultHandler to the function's input parameters :
public void getUserInfo(String id, ResultHandler<UserInfo> handler) {
firestore().collection("userInfo").document(id).get().addOnCompleteListener(snap -> {
if (snap.isSuccessful()) {
try {
// failable constructor. use try-catch
UserInfo info = new UserInfo(snap.getResult().getId(), snap.getResult().getData());
handler.onSuccess(info);
} catch (Exception e) {
handler.onFailure(e);
}
} else {
handler.onFailure(snap.getException())
}
});
}
And called service in Activity
FirestoreService.getInstance().getUserInfo("ZsrAdsG5HVYLTZDBeZtkGDlIBW42", new ResultHandler<UserInfo>() {
#Override
public void onSuccess(UserInfo data) {
Log.i("UserInfo", data.id);
}
#Override
public void onFailure(Exception e) {
// getting data failed for some reason
}
});
Using AsyncTask
You can use an AsyncTask. It has 3 steps to it.
1. onPreExecute() - things you want to do before running doInBackground(). This happens in the UI main thread.
2. doInBackground()- the AsyncTask, will do operations in a background thread (the background thread is created by Android so you don't need to worry about it).
3.onPostExecute() - here you can receive any data from the doInBackground method. The postExecute method is executed again, in the UI main thread.
So you can do any I/O operations in doInBackground(), and return the value you received from the server or any other data source, and onPostExecute(), is the equivalent of a completion block in swift.
How to Declare
To use AsyncTask, you need to extend the Android AsyncTask.
So your own AsyncTask declaration will look like this:
private class MyAsyncTask extends AsyncTask<Void, Void, Void> { ... }
What are the 3 generic arguments you ask?
1. Params - the type of the parameters sent to the task upon execution.
2. Progress - the type of the progress units published during the background computation. (Almost always will be Void, unless you care about the actual progress of the operation. Notice this is Void with a capital letter, and not void as the return type).
3. Result - the type of the result of the background computation.
Full Example
private class LongOperation extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... params) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.interrupted();
}
}
return "Executed";
}
#Override
protected void onPostExecute(String result) {
TextView txt = findViewById(R.id.output);
txt.setText(result);
}
}
In the example, I create a fake, long operation, that you can not run on the UI main thread (because it is a blocking operation).
When the operation is finished, it returns a String, and that same String is received in the onPostExecute() method (and remember, onPostExecute() runs on the UI main thread again). So you can change your UI with the String value you received from the long,blocking operation.
If you want the documentation, here it is:
https://developer.android.com/reference/android/os/AsyncTask
Using Observer Pattern
You can also use the observer pattern in your situation.
Create an interface, that has a method onSuccess(). Have an object implement that interface, and whenever you need it, you can call the onSuccess() method.
Example:
public Interface SuccessInterface{
void onSuccess()
}
public class SuccessHandler implements SuccessInterface{
public void onSuccess(){
//success code goes here
}
}
then in your code, have a SucessHandler instantiated, and call onSuccess() when you need to.
For API 26,
CompletionHandler is available.
Check https://developer.android.com/reference/java/nio/channels/CompletionHandler
I have got a Class for a CustomView that has to inner Classes, both implement Runnable to do a Job in a separate Thread.
public class ValueSelector extends LinearLayout{
.....
private class AutoIncrementer implements Runnable {
#Override
public void run() {
if (plusButtonIsPressed) {
incrementValue();
mHandler.postDelayed(new AutoIncrementer(), REPEAT_INTERVAL_MS);
} else {
mHandler.removeCallbacks(this);
Thread.currentThread().interrupt();
}
}
}
private class AutoDecrementer implements Runnable {
#Override
public void run() {
if (minusButtonIsPressed) {
decrementValue();
mHandler.postDelayed(new AutoDecrementer(), REPEAT_INTERVAL_MS);
} else {
mHandler.removeCallbacks(this);
Thread.currentThread().interrupt();
}
}
}
}
How to clean them up properly?
Do they get Destroyed automatically when the Activity hosting those CustomViews gets destroyed?
Cheers
It will not get destroyed causing a memory leak, as your thread will have a strong reference to your view, and hence your activity.
Make the inner class static and hold weak reference to variables you need in run method.
Second thing you can do is interrupt your thread , when you view get detached from the window and have check in the run method if thread got interrupted or not, though not necessary if your thread is not doing too much work.
Here is what your runnable should look like
private static class AutoDecrementer implements Runnable {
AutoDecrementer (ValueSelector valueSelector ){
this.weakRef = new WeakReference<>(valueSelector);
}
#Override
public void run() {
ValueSelector valueSelector = (ValueSelector )weakRef.get();
if(valueSelector == null){
return ;
}
if (valueSelector.minusButtonIsPressed) {
valueSelector .decrementValue();
valueSelector .mHandler.postDelayed(new AutoDecrementer(), REPEAT_INTERVAL_MS);
} else {
valueSelector.mHandler.removeCallbacks(this);
Thread.currentThread().interrupt();
}
}
}
I have not checked for any errors.
No, it will cause error if Activity is destroyed while timer event is still pending. To avoied that, use WeakReference to some object, decrementing value.
But, generally it is bad practice - to mix UI and some ligic, because it is difficule to test. Consider using rxJava library, this will look like
Subscriptioin s = Observable.just(100, TimeUnit.Milliseconds)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.computation())
.subscribe(t -> decrementValue());
in your onPause() method cancel that actioin by
if (s != null && !s.inUnsubscribed()) {
s.unsubscribe();
s = null;
}
I have a custom adapter that it's associated with a ListView in my MainActivity class and when I press on one of the items of the List (setOnItemClickListener method) I execute an AsyncTask to retrieve the info from the database and send it into a bundle.
Therefore, I have to wait until the AsyncTask finishes to send the info retrieved in the bundle.
For this purpose, I created an interface:
public interface OnCarListener {
void onCarCompleted(String c);
void onCarError(String error);
}
And I have my AsyncTask in another class:
class findCar extends AsyncTask<Integer, Integer, String> {
private final OnCarListener mListener;
public findCar(OnCarListener listener)
{
mListener = listener;
}
protected void onPreExecute() {
}
protected String doInBackground(Integer... idCar) {
String nameCar = "";
//Here the code to retrive the info
nameCar = obj.getString("name");
//Now nameCar = "Car1"
} catch (Exception ex) {
}
return nameCar;
}
protected void onProgressUpdate() {
}
protected void onPostExecute(String c) {
if (mListener != null) {
mListener.onCarCompleted(c);
}
}
}
And I execute my AsyncTask (in my MainActivity) as follows:
new findCar(new OnCarListener()
{
#Override
public void onCarCompleted(String c) {
synchronized (c)
{
name = c;
}
}
#Override
public void onCarError(String error) {
}
}).execute(idCar);
And after executing the AsyncTask I throw the bundle:
bundle.putString("name", name);
Note: I send more info with the bundle but I omitted it to simplify the question.
It should work in my opinion but in the first click in one element of the List the name isn't being passed by the bundle, just in the second and the rest of the clicks I made at the same or in the rest elements of the List, it works.
Expected result: AsyncTask will be executed and until it finishes the rest of the code shouldn't work. It is clear that it's not what it's doing right now.
What I want to know: Why the synchronized doesn't work in the first iteration? I mean, when I have the List and I click on one of the elements of the List the information of the element it's show (in another Activity) but the value name it's not shown.
If I go back and I do one or more clicks on the same element (or a different one) from the List, in all of them appears the value name correctly.
Why in the first iteration it doesn't work?
And I have another question: Why if I quit the synchronized as adelphus said in his answer, any of the times that I click on the elements of the List the value name appears?
I tried the solution in this question: synchronized not synchronizing but still doesn't work.
What could I do? (Without changing the logic of the program)
Thanks in advance!
I'm not sure you understand what synchronized does - it creates an exclusive lock on a given object to create ordered access to a section of code. When multiple threads attempt to enter a synchronized block (with the same lock object), only one thread will be allowed to continue into the block at a time.
In your code, you're synchronizing on the String parameter c. Since no other threads will be accessing this parameter, synchronized has no effect here.
Since your interface callback is being called on the UI thread (via onPostExecute()), you can just set the bundle value in the callback:
void amethod() {
final Bundle bundle = new Bundle();
new findCar(new OnCarListener()
{
#Override
public void onCarCompleted(String c) {
bundle.putString("name", c);
}
#Override
public void onCarError(String error) {
}
}).execute(idCar);
}
So my problem is that i have a AsyncTask that scraps html from a page on a server so i used Jsoup as a library .
so the problem is that i want to set a timeout to cancel the Task if i don't receive any data from the page and display that there is a "communication error " on a toast
is there anyway to kill or stop the asynctask within it self and return a result on onPostExecute
{
private class getPageTitle extends AsyncTask<Void, Void, String> {
String title;
#Override
protected void onPreExecute() {
super.onPreExecute();
connectServerProgressDialog = new ProgressDialog(LoginScreen.this);
connectServerProgressDialog.setTitle("CheckingServer");
connectServerProgressDialog.setMessage("Loading...");
connectServerProgressDialog.setIndeterminate(true);
connectServerProgressDialog.show();
}
#Override
protected String doInBackground(Void... params) {
try {
// Connect to the web site
Document document = Jsoup.connect(CONNECT_URL).get();
title = document.title();
} catch (IOException e) {
e.printStackTrace();
}
return null ;
}
#Override
protected void onPostExecute(String result) {
if(result!=null){
switch (title) {
case "0":
Toast.makeText(LoginScreen.this,"offline",Toast.LENGTH_SHORT).show();
connectServerProgressDialog.dismiss();
break;
case "1":
connectServerProgressDialog.dismiss();
Toast.makeText(LoginScreen.this,"Connected",Toast.LENGTH_SHORT).show();
break;
}}else{
Toast.makeText(LoginScreen.this,"Communication error",Toast.LENGTH_SHORT).show();
}
}
}}
I have a convention that I use for AsyncTask subclasses.
Define an inner interface for the client to use. This decouples the client class so that the AsyncTask can be re-used. The interface is named in the form blahblahListener.
The interface has two methods of the form blahblahCompleted() and blahblahException().
Accept a callback object (listener) that is an implementation of that interface. This is either passed in the AsyncTask constructor or set with a setListener() method.
Hold that listener reference in a WeakReference field so that if the listener goes away before the task completes, the listener can still be garbage-collected.
Define a field to hold an Exception. If an exception occurs in the background method, this field remembers the exception in order to report it to the client.
In the onPostExecute() method, check if the Exception field is null. If it is, call blahblahCompleted() with the result. If it isn't, call blahblahException() with the exception. Also check if the WeakReference is still valid.
For killing the task, you can have a timeout set on your connection. Then when your connection times out, you will get an exception, which is remembered and reported.
So using that convention, your code would look like this:
public class WebPageTitleRemoteTask extends AsyncTask<URL, Void, String> {
private WeakReference<WebPageTitleRetrievalListener> mListener;
private Exception mException;
public WebPageTitleRemoteTask(WebPageTitleRetrievalListener listener) {
super();
mListener = new WeakReference<WebPageTitleRetrievalListener>(listener);
}
#Override
protected String doInBackground(URL... params) {
String title = null;
try {
// Connect to the web site
Document document = Jsoup.connect(params[0]).get();
title = document.title();
} catch (IOException e) {
mException = e;
}
return title;
}
#Override
protected void onPostExecute(String result) {
WebPageTitleRetrievalListener listener = mListener.get();
if (listener != null) {
if (mException == null) {
listener.webPageTitleRetrieved(result);
} else {
listener.webPageTitleRetrievalException(mException);
}
}
}
public static interface WebPageTitleRetrievalListener {
public void webPageTitleRetrieved(String title);
public void webPageTitleRetrievalException(Exception e);
}
}
And your client code would look something like this, with your Activity implementing that inner interface:
.
.
.
connectServerProgressDialog = new ProgressDialog(LoginScreen.this);
connectServerProgressDialog.setTitle("CheckingServer");
connectServerProgressDialog.setMessage("Loading...");
connectServerProgressDialog.setIndeterminate(true);
connectServerProgressDialog.show();
new WebPageTitleRemoteTask(this).execute(url);
.
.
.
#Override
public void webPageTitleRetrieved(String title) {
if (isFinishing()) return;
connectServerProgressDialog.dismiss();
Toast.makeText(this, "title = " + title, Toast.LENGTH_SHORT).show();
}
#Override
public void webPageTitleRetrievalException(Exception e) {
if (isFinishing()) return;
connectServerProgressDialog.dismiss();
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
NOTE: Because the listener is held in a WeakReference, you can't use an anonymous inner class for the listener, because the reference will go away almost immediately and be eligible for garbage collection.
I use this convention consistently, and the extra boilerplate code in the AsyncTask subclass makes it a lot cleaner to use in the client class.
The below method onReceivedTitlegets called 2-3 times with in a second when webview url changes. I want to call a method in it, when onReceivedTitle is being called last time. I am doing this because I just want to monitor url changes with in webview. shouldOverrideUrlLoading is not getting called when url changes through ajax.
class MyWebChromeClient extends WebChromeClient {
#Override
public void onReceivedTitle(WebView view, String title) {
Log.v("onReceivedTitle", "=>" + title);
// callAMehod();
super.onReceivedTitle(view, title);
}
}
If you want to throttle how often a method call causes another method call you can do so for example via a Handler. The simplest version enqueues a delayed message on the first call and any subsequent call while there is an enqueued message will not enqueue a new one. That results in 1 call every X time to go though - but it take at least that amount of time until the first action happens.
Example implementation (you can put that class unmodified somewhere in your code)
public abstract class ThrottleExecutor {
private final long mMinDelay;
public ThrottleExecutor(long minDelay) {
mMinDelay = minDelay;
}
/** Implement to do something */
public abstract void doThrottled();
public final void scheduleExecution() {
if (mHandler.hasMessages(0)) {
// message already enqueued, do nothing
} else {
// otherwise enqueue a message for later
mHandler.sendEmptyMessageDelayed(0, mMinDelay);
}
}
public final void cancelExecution() {
mHandler.removeMessages(0);
}
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
#Override
public void handleMessage(Message msg) {
doThrottled();
}
};
}
And then use it for example like so
class Usage {
private ThrottleExecutor mThrottle = new ThrottleExecutor(2000) {
#Override
public void doThrottled() {
// happens at most every 2000ms
methodToBeThrottled();
}
};
void methodThatHappensTooOften() {
mThrottle.scheduleExecution();
}
void methodToBeThrottled() {
Log.d("TAG", "triggered at 2000ms before");
}
}
You might want to use Handler and do something like this:
class MyWebChromeClient extends WebChromeClient {
private boolean mOnReceivedTitleInvoked;
#Override
public synchronized void onReceivedTitle(final WebView view, final String title) {
if (!mOnReceivedTitleInvoked) {
mOnReceivedTitleInvoked = true;
Log.v("onReceivedTitle", "=>" + title);
handler.postDelayed(new Runnable() {
#Override
public void run() {
super.onReceivedTitle(view, title);
mOnReceivedTitleInvoked = false;
}
}, 1000);
}
}
}
Although you might want to reconsider the onReceivedTitle behaviour.