Android App Memory утечки с повторными звонками залпа - PullRequest
0 голосов
/ 25 апреля 2019

Я посмотрел почти на каждый поток переполнения стека в отношении залпа и утечек, обработчиков и утечек, слабых ссылок и чуть не стал морально парализованным. Моя борьба заключалась в переводе техник из статей в мой код.

По сути, я пытаюсь «пинговать» локальный сервер внутри фрагмента в приложении для Android, чтобы узнать, есть ли он там или нет, используя базовый запрос получения залпа. Если это так, я загружаю страницу, а если нет, я загружаю пустую страницу и продолжаю вращать "ожидание соединения".

Моя проблема в том, что я получаю значительные утечки при повторных звонках на залп. Я надеюсь, что кто-то с реальным опытом борьбы с этими проблемами мог бы помочь. Я прошу, чтобы ответы были в контексте моего размещенного кода. Если бы статьи имели для меня смысл, я бы не стал публиковать здесь.

Вот лишь несколько заметных статей, которые я прочитал, но, вероятно, еще 100, которые я прочитал в блогах и переполнены стеками, пытаясь понять проблему.

Утечка активности при использовании залповых слушателей

https://www.smashingmagazine.com/2017/03/simplify-android-networking-volley-http-library/

http://blog.nimbledroid.com/2016/09/06/stop-memory-leaks.html

Я могу избавиться от утечки, если я не вызову isOnline (), но, очевидно, программа больше не делает то, что мне нужно, поэтому я знаю, что утечка имеет отношение как к залпу, так и к обработчику.

открытый класс 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: Я реализовал слабые ссылки для обработчика и запуска, и, похоже, это не улучшило ситуацию.

открытый класс 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);
    }
};

}

...