Second batch of collected issues, mainly covering WebView memory management, cookie synchronization, and other development details.

WebView Memory Leaks

Avoid declaring WebView directly in XML layout, as the WebView may hold a reference to the Activity’s Context even after the Activity is destroyed, preventing memory release. Correct approaches:

Use ApplicationContext

1
WebView webView = new WebView(getApplicationContext());

Proper Lifecycle Handling in Fragments

1
2
3
4
5
6
@Override
public void onDetach() {
    super.onDetach();
    webView.removeAllViews();
    webView.destroy();
}

Process Management via the android:process Attribute

You can assign different components to separate processes in the manifest:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<application
    android:process="com.processkill.p1">
    <activity
        android:name="com.processkill.A"
        android:process="com.processkill.p2">
    </activity>
    <activity
        android:name="com.processkill.B"
        android:process="com.processkill.p3">
    </activity>
</application>

Avoiding Memory Leaks from Static Drawables

Romain Guy wrote a classic article: Avoid Memory Leaks on Android. While somewhat dated now – Drawable.setCallback() has used WeakReference since Android 4.0.1 – holding Drawables via static fields remains a bad practice.

The Android framework itself clears the previous drawable reference when setting a new background:

1
2
3
4
5
6
7
8
/*
 * Regardless of whether we're setting a new background or not, we want
 * to clear the previous drawable.
 */
if (mBackground != null) {
    mBackground.setCallback(null);
    unscheduleDrawable(mBackground);
}

WebView URL Parameter Encoding

When appending parameters to a URL, the values must be URL-encoded. The old NameValuePair approach has been deprecated since API 23. You can implement it yourself:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
JSONObject json = new JSONObject();
Iterator<String> keys = json.keys();
StringBuilder stringBuilder = new StringBuilder();
try {
    while (keys.hasNext()) {
        String key = keys.next();
        String value = json.optString(key);
        if (value != null) {
            stringBuilder.append(URLEncoder.encode(key, "UTF-8"))
                    .append("=")
                    .append(URLEncoder.encode(value, "UTF-8"));
        }
    }
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}

The underlying logic is the same as NameValuePair – both do two toString() calls internally.

When using OkHttpClient, you need to manually sync cookies to WebView’s CookieManager. I spent some time debugging with packet capture before realizing the issue.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Uri uri = API.getUri();

HttpUrl httpUrl = new HttpUrl.Builder()
        .scheme(uri.getScheme())
        .host(uri.getHost())
        .build();

OkHttpClient okHttpClient = ClientManager.getInstance();
CookieJar cookieJar = okHttpClient.cookieJar();

List<Cookie> cookies = cookieJar.loadForRequest(httpUrl);

for (Cookie cookie : cookies) {
    if (cookie != null) {
        String cookieString = cookie.name() + "=" + cookie.value() + "; domain=" + cookie.domain();
        cookieManager.setCookie(httpUrl.toString(), cookieString);
    }
}

The key insight: CookieManager.setCookie() takes a full URL as its first parameter, not just the host:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * Sets a cookie for the given URL. Any existing cookie with the same host,
 * path and name will be replaced with the new cookie. The cookie being set
 * will be ignored if it is expired.
 *
 * @param url the URL for which the cookie is to be set
 * @param value the cookie as a string, using the format of the 'Set-Cookie'
 *              HTTP response header
 */
public abstract void setCookie(String url, String value);

References