I've looked at just about every stack overflow thread on volley & leaks, handlers & leaks, weak references and have just about become mentally paralyzed. My struggle has been translating the techniques in the articles to my code.
I'm essentially trying to "ping" a local server within a fragment in an android app to see if it is there or not using a basic volley get request. If it is, I load the page and if it isn't, I load a blank page and keep the "waiting for connection" spinner up.
My issue is that I'm getting significant leaks with the repeated calls to volley. I'm hoping someone with some real world experience fighting these issues could help. I do request that the answers be in the context of my posted code. If the articles were making sense to me, I wouldn't be posting here.
Here are just a few notable articles I've read, but there are probably 100 more that I've gone through in blogs and stack overflow trying to understand the issue.
Activity leak while using volley listeners
https://www.smashingmagazine.com/2017/03/simplify-android-networking-volley-http-library/
http://blog.nimbledroid.com/2016/09/06/stop-memory-leaks.html
I can get rid of the leak if I don't call isOnline(), but obviously the program no longer does what I need it to, so I know the leak has something to do with either volley or the handler.
public class DeviceFragment extends Fragment {
Handler handler;
boolean online;
boolean online_prev;
WebView webView;
private static final String TAG = "DeviceFragment";
ProgressBar spinner;
TextView spinnertext;
int toastCount;
boolean onlineBoolean;
boolean isDestroyed;
RequestQueue queue;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_device, container, false);
queue = Volley.newRequestQueue(getActivity()); // moved to help with leaks
spinner = view.findViewById(R.id.progressBar1);
spinnertext = view.findViewById(R.id.progressBar1text);
online = false;
online_prev = false;
toastCount = 1;
handler = new Handler();
isDestroyed = false;
spinner.setVisibility(View.VISIBLE);
spinnertext.setVisibility(View.VISIBLE);
webView = view.findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new myWebClient() {
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
view.loadUrl("about:blank");
Toast.makeText(getActivity().getApplicationContext(), "Waiting For Connection", Toast.LENGTH_LONG).show();
}
});
// New method to get to WiFi settings menu
Button wifisettings = view.findViewById(R.id.WiFiSettings);
wifisettings.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(android.provider.Settings.ACTION_WIFI_SETTINGS));
}
});
Button connect = view.findViewById(R.id.connect);
connect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
webView.loadUrl("http://10.123.40.2");
}
});
handler.postDelayed(runnableCode, 1000);
return view;
}
@Override
public void onResume() {
super.onResume();
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
@Override
public void onPause() {
super.onPause();
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
private Runnable runnableCode = new Runnable() {
@Override
public void run() {
online = onlineBoolean;
if(isDestroyed == false){
isOnline();
}
if (online == true && online_prev == false) {
webView.loadUrl("http://10.123.40.2");
spinner.setVisibility(View.GONE);
spinnertext.setVisibility(View.GONE);
toastCount = 0;
} else if (online == true && online_prev == true) {
// do nothing
} else if (online == false && online_prev == true) {
// do nothing
} else {
webView.loadUrl("about:blank");
spinner.setVisibility(View.VISIBLE);
spinnertext.setVisibility(View.VISIBLE);
if (toastCount == 0) {
Toast.makeText(getActivity().getApplicationContext(), "Wi-Fi Connection Lost - Please Check Wi-Fi Settings", Toast.LENGTH_LONG).show();
toastCount = 1;
}
}
online_prev = online;
//handler = new Handler(); // leaks?
handler.removeCallbacksAndMessages(null); // Not sure if this helps with leaks?
handler.postDelayed(this, 5000);
}
};
public class myWebClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
}
public void isOnline() {
//RequestQueue queue = Volley.newRequestQueue(getActivity()); // Causes huge leak. Move to public
String url = "http://10.123.40.2";
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
onlineBoolean = true;
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onlineBoolean = false;
}
});
queue.add(stringRequest);
}
public void onDestroy () {
handler.removeCallbacks(runnableCode);
isDestroyed = true;
super.onDestroy ();
}
}
UPDATE: I've implemented weak referenced for the handler and runnable and these didn't seem to improve things.
public class DeviceFragment extends Fragment {
private final NimbleHandler nimblehander = new NimbleHandler(this);
private static class NimbleHandler extends Handler{
private WeakReference<DeviceFragment> weakReference;
public NimbleHandler(DeviceFragment activity) {
weakReference = new WeakReference<>(activity);
}
@Override public void handleMessage(Message message){
super.handleMessage(message);
}
}
private static class NimbleRunnable implements Runnable {
private WeakReference<DeviceFragment> weakReference;
public NimbleRunnable(DeviceFragment activity) {
weakReference = new WeakReference<>(activity);
}
@Override public void run(){
while(true);
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_device, container, false);
queue = Volley.newRequestQueue(getActivity());
spinner = view.findViewById(R.id.progressBar1);
spinnertext = view.findViewById(R.id.progressBar1text);
online = false;
online_prev = false;
toastCount = 1;
isDestroyed = false;
spinner.setVisibility(View.VISIBLE);
spinnertext.setVisibility(View.VISIBLE);
webView = view.findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new myWebClient() {
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
view.loadUrl("about:blank");
Toast.makeText(getActivity().getApplicationContext(), "Waiting For Connection", Toast.LENGTH_LONG).show();
}
});
nimblehander.post(runnableCode);
return view;
}
private final Runnable runnableCode = new NimbleRunnable(this) {
@Override
public void run() {
online = onlineBoolean;
if(isDestroyed == false){
isOnline();
}
// omitted rest of code to keep short
nimblehander.postDelayed(this, 5000);
}
};
}