Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save & restoring WebView in an embedded PhoneGap app

I have an Android app, which uses an embedded PhoneGap WebView. I've successfully implemented CordovaInterface on my activity and the app starts and functions how it should.

When I pause the app (switching to an other app or tabbing Home), I save the WebView's state with the saveState method, which should be restored when the app starts again (this approach works in an app without PhoneGap).

However, when the app starts again, I try to to restore the state (with the restoreState method) without loading an url first (since I want to use the last state). This causes an error because PhoneGap expects an url to be loaded (at least that's what I gather from the exception).

My question is: How can I correctly save and restore the WebView's state in an embedded PhoneGap WebView?

My onCreate method:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    webView = (CordovaWebView) findViewById(R.id.webView);

    if (savedInstanceState == null) {
        savedInstanceState = restoreFromPreferences();
    }
    if (savedInstanceState == null) {
        webView.loadUrl("file:///android_asset/www/index.html");
    } else {
        webView.restoreState(savedInstanceState);
        webView.loadUrlIntoView(savedInstanceState.getString("url"));
    }
}

My onPause method (similar logic in onSaveInstanceState):

@Override
protected void onPause() {
    super.onPause();

    Bundle out = new Bundle();
    webView.saveState(out);

    saveToPreferences(out);
}

Error when closing/pausing app (solved - see update 1):

I get an error, when I close the app. This is probably related, but I don't see how:

01-09 11:44:50.181: E/ActivityThread(2068): Activity my.package.MainActivity has leaked IntentReceiver org.apache.cordova.NetworkManager$1@a6bc4020 that was originally registered here. Are you missing a call to unregisterReceiver()?
01-09 11:44:50.181: E/ActivityThread(2068): android.app.IntentReceiverLeaked: Activity my.package.MainActivity has leaked IntentReceiver org.apache.cordova.NetworkManager$1@a6bc4020 that was originally registered here. Are you missing a call to unregisterReceiver()?
...

Error when starting app again (solved - see update 1):

This causes an error, because the WebView tries to load an url which is null:

01-09 11:38:22.813: E/AndroidRuntime(1979): FATAL EXCEPTION: main
01-09 11:38:22.813: E/AndroidRuntime(1979): java.lang.NullPointerException
01-09 11:38:22.813: E/AndroidRuntime(1979):     at java.lang.String.indexOf(String.java:994)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at org.apache.cordova.CordovaWebView.loadUrlNow(CordovaWebView.java:499)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at org.apache.cordova.CordovaWebView.loadUrl(CordovaWebView.java:384)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at org.apache.cordova.CordovaWebViewClient.onPageFinished(CordovaWebViewClient.java:298)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at android.webkit.CallbackProxy.handleMessage(CallbackProxy.java:327)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at android.os.Handler.dispatchMessage(Handler.java:99)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at android.os.Looper.loop(Looper.java:137)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at android.app.ActivityThread.main(ActivityThread.java:4745)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at java.lang.reflect.Method.invokeNative(Native Method)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at java.lang.reflect.Method.invoke(Method.java:511)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
01-09 11:38:22.813: E/AndroidRuntime(1979):     at dalvik.system.NativeStart.main(Native Method)

UPDATE 1:

Both errors are resolved but replaced by a new one.

The first error was because webView.handleDestroy() in onDestroy() was not called.

The second error was due to field baseUrl in CordovaWebView which is not set. Currently I'm trying to resolve that by saving the last visited url into SharedPreferences when onPause() / onSaveInstanceState() occur:

        String url = webView.peekAtUrlStack();
        out.putString("url", url);
        webView.handlePause(true);

In onResume(), I load the url in SharedPreferences:

    if (savedInstanceState != null) {
        webView.restoreState(savedInstanceState);           
        webView.loadUrlIntoView(savedInstanceState.getString("url"));
    }

Now I get another (nasty) error. Strange thing is that the app immediately crashes (without notification). After a few tries it starts anyway. Here is the error:

01-09 15:11:19.869: A/libc(3392): Fatal signal 11 (SIGSEGV) at 0x00000008 (code=1), thread 3404 (WebViewCoreThre)
01-09 15:11:19.925: I/ActivityManager(281): Displayed my.package/.MainActivity: +197ms
01-09 15:11:19.973: I/DEBUG(86): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
01-09 15:11:19.973: I/DEBUG(86): Build fingerprint: 'generic/vbox86tp/vbox86tp:4.1.1/JRO03L/eng.dan.20121106.232935:userdebug/test-keys'
01-09 15:11:19.973: I/DEBUG(86): pid: 3392, tid: 3404, name: WebViewCoreThre  >>> my.package <<<
01-09 15:11:19.973: I/DEBUG(86): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000008
01-09 15:11:20.249: I/DEBUG(86):     eax 00000000  ebx b6b61c98  ecx 00000000  edx 00000001
...

UPDATE 2:

Restoring the WebView state occurred twice, which caused problems. So removed it from onResume.

like image 394
asgoth Avatar asked Jan 09 '13 11:01

asgoth


1 Answers

The problems with restoring an embedded CordovaWebView were because the interactions with the CordovaWebView in onPause(), onResume(), onCreate() and onDestroy() did not occur correctly. When starting to fix, restoreState() occurred twice which caused further problems.

onCreate:

Next to restoring the state, e need to setup the CordovaWebView base url by using method loadUrlIntoView():

webView.restoreState(savedInstanceState);
webView.loadUrlIntoView(savedInstanceState.getString("url"));

onDestroy:

We need to execute handleDestroy() to cleanup all PhoneGap related stuff, like the PluginManager, broadcast receivers, ...:

@Override
public void onDestroy() {
    super.onDestroy();

    webView.handleDestroy();
}

onPause:

In onPause(), we store the CordovaWebView's state. We lookup the last registered url and we store it in SharedPreferences. Also, handlePause() needs to be called.

@Override
protected void onPause() {
    super.onPause();

    String url = webView.peekAtUrlStack();
    webView.handlePause(true);

    Bundle out = new Bundle();
    webView.saveState(out);
    out.putString("url", url);

    saveToPreferences(out);
}

onResume:

In onResume() we need to call CordovaWebView's handleResume():

webView.handleResume(true, false);

Conclusion:

Embedding a CordovaWebView and saving and restoring its state is possible, but because you have to know a lot of internals from DroidGap & CordovaWebView, it is NOT advisable to do so.

I hope my answer will help somebody.

like image 76
asgoth Avatar answered Sep 20 '22 00:09

asgoth