Android: Using Webview Outside an Activity Context

Android: Using WebView outside an Activity context

Correct me if I am wrong but the correct answer to this question is that there is NO possible way to use a WebView in the background while the user is doing other things on the phone without interrupting the user by means of an Activity.

I have applied both Randy and Code_Yoga's suggestions: Using an activity with "Theme.NoDisplay" to launch a background service with a WebView to do some work. However even though no view is visible the switching to that activity for that second to start the services interrupts the user (ex. like pausing a running game that was being played).

Totally disastrous news for my app so I am still hoping someone will give me a way to use a WebView that does not need an Activity (or a substitute for a WebView that can accomplish the same)

Showing an AlertDialog from a Webview outside of an Activity

You can display the Dialog from the service, by setting the window type as TYPE_SYSTEM_ALERT. Remember to communicate back the action taken by the user using the JsResult instance passed to onJsAlert.

    // maintain reference to JsResult instance passed to onJsAlert, in order
// communicate back the action taken by the user.
private JsResult mResult;

webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
mResult = result;
AlertDialog dialog = new AlertDialog.Builder(MyService.this)
.setTitle("Custom Dialog")
.setMessage("This is our custom dialog, not the one served by JsDialogHelper")
.setOnCancelListener(new CancelListener())
.setNegativeButton("Cancel", new CancelListener())
.setPositiveButton("Ok", new PositiveListener())
.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();

return true;
}
});

private class CancelListener implements DialogInterface.OnCancelListener,
DialogInterface.OnClickListener {

@Override
public void onCancel(DialogInterface dialogInterface) {
mResult.cancel();
}

@Override
public void onClick(DialogInterface dialogInterface, int i) {
mResult.cancel();
}
}

private class PositiveListener implements DialogInterface.OnClickListener {

@Override
public void onClick(DialogInterface dialogInterface, int i) {
mResult.confirm();
}
}

Add the required permissions to the manifest file:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

Alternate solution:
If the web application can be built specific to android webview, then you can create Javascript interfaces which will allow Javascript code to directly invoke the Android code which in turn displays the dialog for you. This avoids the JsDialogHelper route.

  1. Define a javascript interface:
public class WebAppInterface {
Context context;

WebAppInterface(Context c) {
context = c;
}

// Annotation required for api >= 17
@JavascriptInterface
public void showDialog() {
AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle("Custom Dialog")
.setMessage("This is our custom dialog, not the one served by JsDialogHelper")
.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();
}
}

  1. Bind the interface between Javascript and Android code, using addJavascriptInterface.

webView.addJavascriptInterface(new WebAppInterface(this), "Android");


  1. Use the interface in Javascript code.
<input type="button" value="Display dialog" onClick="displayDialog()" />
<script>
function displayDialog() {
//alert("Javascript Dialog");
Android.showDialog();
}
</script>
</input>

Android - How can I Attach a WebView to an Activity after creating it with the Application Context

So, we've actually run into the same issue (using a WebView in a retained Fragment to prevent page reloads, while not leaking an Activity context) and the short answer seems to be that there's no really clean way to do so. I've even tried a custom WebView subclass that returned the host Activity's window token instead of the WebView's own token, to no success.

We ended up using reflection, as you suggested, to modify the underlying context:

public static boolean setContext(View v, Context ctx) {
try {
final Field contextField = View.class.getDeclaredField("mContext");
contextField.setAccessible(true);
contextField.set(v, ctx);
return (v.getContext() == ctx);
} catch (IllegalAccessException | NoSuchFieldException e) {
Log.e(TAG, String.valueOf(e), e);
return false;
}
}

which just sets the mContext field on the View instance and returns true if it was successfully modified. However, I saw another suggestion recently (and I haven't tested this, so YMMV) to use MutableContextWrapper. So you would inflate the WebView with the Activity Context, wrapped in a MutableContextWrapper. Then, when you need to release the previous reference, you'd cast that WebView's Context to a MutableContextWrapper, and then set the base Context to the new Activity. So something like this to inflate the layout:

MutableContextWrapper contextWrapper = new MutableContextWrapper(activity);

WebView webView = (WebView) LayoutInflater.from(contextWrapper)
.inflate(R.layout.your_webview_layout, theParent, false);

Then, to re-attach to a new Activity:

if (webView.getContext() instanceof MutableContextWrapper) {
((MutableContextWrapper) webView.getContext()).setBaseContext(newActivity);
}

How to use WebView in an IntentService?

There are no GUI-less web browser available for Android

While I have not tried it recently, WebView at least used to have no particular requirement for displaying its UI. I used WebView from within a Service, in my case for a JavaScript interpreter (back before we had better options for that).

Now, an IntentService is not a good choice. WebView is largely asynchronous, and IntentService will destroy itself before WebView gets a chance to do its work. Use a regular Service, where you control the Service lifetime, so you can call stopSelf() only when you are ready to do so.

I want to use a WebView instance which can only run on an UI thread.

The relationship between WebView and threads is complicated. But, back when I last tried it, IIRC, a WebView that is not actually appearing on the screen did not need the main application thread. But, as I noted, WebView does most of its work asynchronously. You may find that you do not need a background thread of your own.

Is there a way to serialize the context of my activity so I can use this context to create my WebView?

No, nor would it address any of your concerns.

How to use Androids ContextWrapper class to emulate an activity?

That is not possible, nor would it address any of your concerns.

Can WebView be used inside a service?

No, a WebView should not be used inside a service, and it really doesn't make sense to, anyway. If you're loading your WebView with the intention of scraping the html contained in it, you might as well just run an HttpGet request, like this --

public static String readFromUrl( String url ) {
String result = null;

HttpClient client = new DefaultHttpClient();

HttpGet get = new HttpGet( url );

HttpResponse response;
try {
response = client.execute( get );
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream is = entity.getContent();
BufferedReader reader = new BufferedReader(
new InputStreamReader( is ) );
StringBuilder sb = new StringBuilder();

String line = null;
try {
while( ( line = reader.readLine() ) != null )
sb.append( line + "\n" );
} catch ( IOException e ) {
Log.e( "readFromUrl", e.getMessage() );
} finally {
try {
is.close();
} catch ( IOException e ) {
Log.e( "readFromUrl", e.getMessage() );
}
}

result = sb.toString();
is.close();
}

} catch( Exception e ) {
Log.e( "readFromUrl", e.getMessage() );
}

return result;
}


Related Topics



Leave a reply



Submit