Merge pull request #1 from brycecurtis/webview

Load multi-page apps in same webview and update pause/resume for consiste
This commit is contained in:
Bryce Curtis 2011-10-21 14:43:13 -07:00
commit cdeddf1bb2
6 changed files with 188 additions and 147 deletions

View File

@ -24,21 +24,18 @@ App.prototype.clearCache = function() {
};
/**
* Load the url into the webview.
* Load the url into the webview or into new browser instance.
*
* @param url The URL to load
* @param props Properties that can be passed in to the activity:
* wait: int => wait msec before loading URL
* loadingDialog: "Title,Message" => display a native loading dialog
* hideLoadingDialogOnPage: boolean => hide loadingDialog when page loaded instead of when deviceready event occurs.
* loadInWebView: boolean => cause all links on web page to be loaded into existing web view, instead of being loaded into new browser.
* loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error
* errorUrl: URL => URL to load if there's an error loading specified URL with loadUrl(). Should be a local URL such as file:///android_asset/www/error.html");
* keepRunning: boolean => enable app to keep running in background
* clearHistory: boolean => clear webview history (default=false)
* openExternal: boolean => open in a new browser (default=false)
*
* Example:
* App app = new App();
* app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000});
* navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000});
*/
App.prototype.loadUrl = function(url, props) {
PhoneGap.exec(null, null, "App", "loadUrl", [url, props]);

View File

@ -23,13 +23,18 @@ if (typeof PhoneGap === "undefined") {
* onDestroy Internal event fired when app is being destroyed (User should use window.onunload event, not this one).
*
* The only PhoneGap events that user code should register for are:
* onDeviceReady
* onResume
* deviceready PhoneGap native code is initialized and PhoneGap APIs can be called from JavaScript
* pause App has moved to background
* resume App has returned to foreground
*
* Listeners can be registered as:
* document.addEventListener("deviceready", myDeviceReadyListener, false);
* document.addEventListener("resume", myResumeListener, false);
* document.addEventListener("pause", myPauseListener, false);
*
* The DOM lifecycle events should be used for saving and restoring state
* window.onload
* window.onunload
*/
if (typeof(DeviceInfo) !== 'object') {

View File

@ -78,18 +78,18 @@ public class App extends Plugin {
((DroidGap)this.ctx).clearCache();
}
/**
* Load the url into the webview.
*
* @param url
* @param props Properties that can be passed in to the DroidGap activity (i.e. loadingDialog, wait, ...)
* @throws JSONException
*/
/**
* Load the url into the webview.
*
* @param url
* @param props Properties that can be passed in to the DroidGap activity (i.e. loadingDialog, wait, ...)
* @throws JSONException
*/
public void loadUrl(String url, JSONObject props) throws JSONException {
System.out.println("App.loadUrl("+url+","+props+")");
int wait = 0;
boolean usePhoneGap = true;
boolean clearPrev = false;
boolean openExternal = false;
boolean clearHistory = false;
// If there are properties, then set them on the Activity
HashMap<String, Object> params = new HashMap<String, Object>();
@ -100,11 +100,11 @@ public class App extends Plugin {
if (key.equals("wait")) {
wait = props.getInt(key);
}
else if (key.equalsIgnoreCase("usephonegap")) {
usePhoneGap = props.getBoolean(key);
else if (key.equalsIgnoreCase("openexternal")) {
openExternal = props.getBoolean(key);
}
else if (key.equalsIgnoreCase("clearprev")) {
clearPrev = props.getBoolean(key);
else if (key.equalsIgnoreCase("clearhistory")) {
clearHistory = props.getBoolean(key);
}
else {
Object value = props.get(key);
@ -135,7 +135,7 @@ public class App extends Plugin {
e.printStackTrace();
}
}
((DroidGap)this.ctx).showWebPage(url, usePhoneGap, clearPrev, params);
((DroidGap)this.ctx).showWebPage(url, openExternal, clearHistory, params);
}
/**
@ -146,12 +146,10 @@ public class App extends Plugin {
}
/**
* Clear web history in this web view.
* This does not have any effect since each page has its own activity.
* Clear page history for the app.
*/
public void clearHistory() {
((DroidGap)this.ctx).clearHistory();
// TODO: Kill previous activities?
}
/**
@ -159,7 +157,7 @@ public class App extends Plugin {
* This is the same as pressing the backbutton on Android device.
*/
public void backHistory() {
((DroidGap)this.ctx).endActivity();
((DroidGap)this.ctx).backHistory();
}
/**
@ -186,7 +184,6 @@ public class App extends Plugin {
* Exit the Android application.
*/
public void exitApp() {
((DroidGap)this.ctx).setResult(Activity.RESULT_OK);
((DroidGap)this.ctx).endActivity();
}

View File

@ -103,6 +103,10 @@ public class CallbackServer implements Runnable {
*/
public void init(String url) {
//System.out.println("CallbackServer.start("+url+")");
this.active = false;
this.empty = true;
this.port = 0;
this.javascript = new LinkedList<String>();
// Determine if XHR or polling is to be used
if ((url != null) && !url.startsWith("file://")) {
@ -119,6 +123,16 @@ public class CallbackServer implements Runnable {
}
}
/**
* Re-init when loading a new HTML page into webview.
*
* @param url The URL of the PhoneGap app being loaded
*/
public void reinit(String url) {
this.stopServer();
this.init(url);
}
/**
* Return if polling is being used instead of XHR.
*

View File

@ -10,6 +10,7 @@ package com.phonegap;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.ArrayList;
import java.util.Stack;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Iterator;
@ -105,10 +106,6 @@ import org.xmlpull.v1.XmlPullParserException;
* // (String - default=null)
* super.setStringProperty("loadingPageDialog", "Loading page...");
*
* // Cause all links on web page to be loaded into existing web view,
* // instead of being loaded into new browser. (Boolean - default=false)
* super.setBooleanProperty("loadInWebView", true);
*
* // Load a splash screen image from the resource drawable directory.
* // (Integer - default=0)
* super.setIntegerProperty("splashscreen", R.drawable.splash);
@ -161,13 +158,20 @@ public class DroidGap extends PhonegapActivity {
public CallbackServer callbackServer;
protected PluginManager pluginManager;
protected boolean cancelLoadUrl = false;
protected boolean clearHistory = false;
protected ProgressDialog spinnerDialog = null;
// The initial URL for our app
// ie http://server/path/index.html#abc?query
private String url;
private boolean firstPage = true;
private String url = null;
private Stack<String> urls = new Stack<String>();
// Url was specified from extras (activity was started programmatically)
private String initUrl = null;
private static int ACTIVITY_STARTING = 0;
private static int ACTIVITY_RUNNING = 1;
private static int ACTIVITY_EXITING = 2;
private int activityState = 0; // 0=starting, 1=running (after 1st resume), 2=shutting down
// The base of the initial URL for our app.
// Does not include file name. Ends with /
@ -177,7 +181,6 @@ public class DroidGap extends PhonegapActivity {
// Plugin to call when activity result is received
protected IPlugin activityResultCallback = null;
protected boolean activityResultKeepRunning;
private static int PG_REQUEST_CODE = 99;
// Flag indicates that a loadUrl timeout occurred
private int loadUrlTimeout = 0;
@ -190,10 +193,6 @@ public class DroidGap extends PhonegapActivity {
* The variables below are used to cache some of the activity properties.
*/
// Flag indicates that a URL navigated to from PhoneGap app should be loaded into same webview
// instead of being loaded into the web browser.
protected boolean loadInWebView = false;
// Draw a splash screen using an image located in the drawable resource directory.
// This is not the same as calling super.loadSplashscreen(url)
protected int splashscreen = 0;
@ -236,13 +235,11 @@ public class DroidGap extends PhonegapActivity {
this.loadConfiguration();
// If url was passed in to intent, then init webview, which will load the url
this.firstPage = true;
Bundle bundle = this.getIntent().getExtras();
if (bundle != null) {
String url = bundle.getString("url");
if (url != null) {
this.url = url;
this.firstPage = false;
this.initUrl = url;
}
}
// Setup the hardware volume controls to handle volume control
@ -293,10 +290,6 @@ public class DroidGap extends PhonegapActivity {
// Enable built-in geolocation
WebViewReflect.setGeolocationEnabled(settings, true);
// Create callback server and plugin manager
this.callbackServer = new CallbackServer();
this.pluginManager = new PluginManager(this.appView, this);
// Add web view but make it invisible while loading URL
this.appView.setVisibility(View.INVISIBLE);
root.addView(this.appView);
@ -324,24 +317,16 @@ public class DroidGap extends PhonegapActivity {
*/
private void handleActivityParameters() {
// Init web view if not already done
if (this.appView == null) {
this.init();
}
// If backgroundColor
this.backgroundColor = this.getIntegerProperty("backgroundColor", Color.BLACK);
this.root.setBackgroundColor(this.backgroundColor);
// If spashscreen
this.splashscreen = this.getIntegerProperty("splashscreen", 0);
if (this.firstPage && (this.splashscreen != 0)) {
if ((this.urls.size() == 0) && (this.splashscreen != 0)) {
root.setBackgroundResource(this.splashscreen);
}
// If loadInWebView
this.loadInWebView = this.getBooleanProperty("loadInWebView", false);
// If loadUrlTimeoutValue
int timeout = this.getIntegerProperty("loadUrlTimeoutValue", 0);
if (timeout > 0) {
@ -360,12 +345,12 @@ public class DroidGap extends PhonegapActivity {
public void loadUrl(String url) {
// If first page of app, then set URL to load to be the one passed in
if (this.firstPage) {
if (this.initUrl == null || (this.urls.size() > 0)) {
this.loadUrlIntoView(url);
}
// Otherwise use the URL specified in the activity's extras bundle
else {
this.loadUrlIntoView(this.url);
this.loadUrlIntoView(this.initUrl);
}
}
@ -378,6 +363,11 @@ public class DroidGap extends PhonegapActivity {
if (!url.startsWith("javascript:")) {
LOG.d(TAG, "DroidGap.loadUrl(%s)", url);
}
// Init web view if not already done
if (this.appView == null) {
this.init();
}
this.url = url;
if (this.baseUrl == null) {
@ -401,12 +391,28 @@ public class DroidGap extends PhonegapActivity {
// Handle activity parameters
me.handleActivityParameters();
// Initialize callback server
me.callbackServer.init(url);
// Track URLs loaded instead of using appView history
me.urls.push(url);
me.appView.clearHistory();
// Create callback server and plugin manager
if (me.callbackServer == null) {
me.callbackServer = new CallbackServer();
me.callbackServer.init(url);
}
else {
me.callbackServer.reinit(url);
}
if (me.pluginManager == null) {
me.pluginManager = new PluginManager(me.appView, me);
}
else {
me.pluginManager.reinit();
}
// If loadingDialog property, then show the App loading dialog for first page of app
String loading = null;
if (me.firstPage) {
if (me.urls.size() == 0) {
loading = me.getStringProperty("loadingDialog", null);
}
else {
@ -446,6 +452,7 @@ public class DroidGap extends PhonegapActivity {
// If timeout, then stop loading and handle error
if (me.loadUrlTimeout == currentLoadUrlTimeout) {
me.appView.stopLoading();
LOG.e(TAG, "DroidGap: TIMEOUT ERROR! - calling webViewClient");
me.webViewClient.onReceivedError(me.appView, -6, "The connection to the server was unsuccessful.", url);
}
}
@ -467,12 +474,12 @@ public class DroidGap extends PhonegapActivity {
public void loadUrl(final String url, int time) {
// If first page of app, then set URL to load to be the one passed in
if (this.firstPage) {
if (this.initUrl == null || (this.urls.size() > 0)) {
this.loadUrlIntoView(url, time);
}
// Otherwise use the URL specified in the activity's extras bundle
else {
this.loadUrlIntoView(this.url);
this.loadUrlIntoView(this.initUrl);
}
}
@ -484,10 +491,13 @@ public class DroidGap extends PhonegapActivity {
* @param time The number of ms to wait before loading webview
*/
private void loadUrlIntoView(final String url, final int time) {
// Clear cancel flag
this.cancelLoadUrl = false;
// If not first page of app, then load immediately
if (!this.firstPage) {
this.loadUrl(url);
if (this.urls.size() > 0) {
this.loadUrlIntoView(url);
}
if (!url.startsWith("javascript:")) {
@ -512,7 +522,7 @@ public class DroidGap extends PhonegapActivity {
e.printStackTrace();
}
if (!me.cancelLoadUrl) {
me.loadUrl(url);
me.loadUrlIntoView(url);
}
else{
me.cancelLoadUrl = false;
@ -545,9 +555,22 @@ public class DroidGap extends PhonegapActivity {
* Clear web history in this web view.
*/
public void clearHistory() {
this.clearHistory = true;
if (this.appView != null) {
this.appView.clearHistory();
this.urls.clear();
// Leave current url on history stack
if (this.url != null) {
this.urls.push(this.url);
}
}
/**
* Go to previous page in history. (We manage our own history)
*/
public void backHistory() {
if (this.urls.size() > 1) {
this.urls.pop(); // Pop current url
String url = this.urls.pop(); // Pop prev url that we want to load
this.loadUrl(url);
}
}
@ -684,6 +707,12 @@ public class DroidGap extends PhonegapActivity {
*/
protected void onPause() {
super.onPause();
// Don't process pause if shutting down, since onDestroy() will be called
if (this.activityState == ACTIVITY_EXITING) {
return;
}
if (this.appView == null) {
return;
}
@ -719,6 +748,12 @@ public class DroidGap extends PhonegapActivity {
*/
protected void onResume() {
super.onResume();
if (this.activityState == ACTIVITY_STARTING) {
this.activityState = ACTIVITY_RUNNING;
return;
}
if (this.appView == null) {
return;
}
@ -752,8 +787,6 @@ public class DroidGap extends PhonegapActivity {
if (this.appView != null) {
// Make sure pause event is sent if onPause hasn't been called before onDestroy
this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};");
// Send destroy event to JavaScript
this.appView.loadUrl("javascript:try{PhoneGap.onDestroy.fire();}catch(e){};");
@ -790,62 +823,60 @@ public class DroidGap extends PhonegapActivity {
}
/**
* Display a new browser with the specified URL.
* Load the specified URL in the PhoneGap webview or a new browser instance.
*
* NOTE: If usePhoneGap is set, only trusted PhoneGap URLs should be loaded,
* since any PhoneGap API can be called by the loaded HTML page.
* NOTE: If openExternal is false, only URLs listed in whitelist can be loaded.
*
* @param url The url to load.
* @param usePhoneGap Load url in PhoneGap webview.
* @param clearPrev Clear the activity stack, so new app becomes top of stack
* @param openExternal Load url in browser instead of PhoneGap webview.
* @param clearHistory Clear the history stack, so new page becomes top of history
* @param params DroidGap parameters for new app
* @throws android.content.ActivityNotFoundException
*/
public void showWebPage(String url, boolean usePhoneGap, boolean clearPrev, HashMap<String, Object> params) throws android.content.ActivityNotFoundException {
Intent intent = null;
if (usePhoneGap) {
try {
intent = new Intent().setClass(this, Class.forName(this.getComponentName().getClassName()));
intent.putExtra("url", url);
// Add parameters
if (params != null) {
java.util.Set<Entry<String,Object>> s = params.entrySet();
java.util.Iterator<Entry<String,Object>> it = s.iterator();
while(it.hasNext()) {
Entry<String,Object> entry = it.next();
String key = entry.getKey();
Object value = entry.getValue();
if (value == null) {
}
else if (value.getClass().equals(String.class)) {
intent.putExtra(key, (String)value);
}
else if (value.getClass().equals(Boolean.class)) {
intent.putExtra(key, (Boolean)value);
}
else if (value.getClass().equals(Integer.class)) {
intent.putExtra(key, (Integer)value);
}
}
}
super.startActivityForResult(intent, PG_REQUEST_CODE);
} catch (ClassNotFoundException e) {
e.printStackTrace();
intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.startActivity(intent);
}
}
else {
intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.startActivity(intent);
public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) { //throws android.content.ActivityNotFoundException {
LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);
// If clearing history
if (clearHistory) {
this.clearHistory();
}
// Finish current activity
if (clearPrev) {
this.endActivity();
// If loading into our webview
if (!openExternal) {
// Make sure url is in whitelist
if (url.startsWith("file://") || url.indexOf(this.baseUrl) == 0 || isUrlWhiteListed(url)) {
// TODO: What about params?
// Clear out current url from history, since it will be replacing it
if (clearHistory) {
this.urls.clear();
}
// Load new URL
this.loadUrl(url);
}
// Load in default viewer if not
else {
LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list. Loading into browser instead. (URL="+url+")");
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading url "+url, e);
}
}
}
// Load in default view intent
else {
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.startActivity(intent);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading url "+url, e);
}
}
}
@ -1231,14 +1262,8 @@ public class DroidGap extends PhonegapActivity {
// If our app or file:, then load into a new phonegap webview container by starting a new instance of our activity.
// Our app continues to run. When BACK is pressed, our app is redisplayed.
if (this.ctx.loadInWebView || url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0 || isUrlWhiteListed(url)) {
try {
// Init parameters to new DroidGap activity and propagate existing parameters
HashMap<String, Object> params = new HashMap<String, Object>();
this.ctx.showWebPage(url, true, false, params);
} catch (android.content.ActivityNotFoundException e) {
LOG.e(TAG, "Error loading url into DroidGap - "+url, e);
}
if (url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0 || isUrlWhiteListed(url)) {
this.ctx.loadUrl(url);
}
// If not our application, let default viewer handle
@ -1255,6 +1280,14 @@ public class DroidGap extends PhonegapActivity {
return true;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// Clear history so history.back() doesn't do anything.
// So we can reinit() native side CallbackServer & PluginManager.
view.clearHistory();
}
/**
* Notify the host application that a page has finished loading.
*
@ -1294,11 +1327,6 @@ public class DroidGap extends PhonegapActivity {
t.start();
}
// Clear history, so that previous screen isn't there when Back button is pressed
if (this.ctx.clearHistory) {
this.ctx.clearHistory = false;
this.ctx.appView.clearHistory();
}
// Shutdown if blank loaded
if (url.equals("about:blank")) {
@ -1386,8 +1414,8 @@ public class DroidGap extends PhonegapActivity {
else {
// Go to previous page in webview if it is possible to go back
if (this.appView.canGoBack()) {
this.appView.goBack();
if (this.urls.size() > 1) {
this.backHistory();
return true;
}
@ -1463,17 +1491,6 @@ public class DroidGap extends PhonegapActivity {
*/
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
// If a subsequent DroidGap activity is returning
if (requestCode == PG_REQUEST_CODE) {
// If terminating app, then shut down this activity too
if (resultCode == Activity.RESULT_OK) {
this.setResult(Activity.RESULT_OK);
this.endActivity();
}
return;
}
IPlugin callback = this.activityResultCallback;
if (callback != null) {
callback.onActivityResult(requestCode, resultCode, intent);

View File

@ -51,6 +51,17 @@ public final class PluginManager {
this.loadPlugins();
}
/**
* Re-init when loading a new HTML page into webview.
*/
public void reinit() {
// Stop plugins on current HTML page and discard
this.onPause(false);
this.onDestroy();
this.plugins = new HashMap<String, IPlugin>();
}
/**
* Load plugins from res/xml/plugins.xml
*/