WebView - Change of Orientation

RichardN

Well-Known Member
Licensed User
Longtime User
When the device orientation changes I need to refresh the contents of a WebView. What is the most code-efficient way of doing this without executing another WebView.LoadURL() and downloading the page again?

What is the easiest way of caching the HTML locally and executing WebView.LoadHTML() on re-orientation?

Thanks
 

warwound

Expert
Licensed User
Longtime User
What is the most code-efficient way of doing this without executing another WebView.LoadURL() and downloading the page again?

Using the WebView LoadUrl method isn't necessarily as inefficient as it might seem.

Take a look at the official documentation for the native Android WebView WebSettings.

Look at the Constants section right near the top of the page: LOAD_CACHE_ELSE_NETWORK, LOAD_CACHE_ONLY etc.

By default the WebView will cache documents and re-use them - an orientation change does not mean that an entire (previously loaded) web page will be downloaded again.

You can further customise the WebView WebSettings with my WebViewSettings library.

Martin.
 
Upvote 0

RichardN

Well-Known Member
Licensed User
Longtime User
Many thanks Martin & NJDude.

Executing LOAD_CACHE_ELSE_NETWORK does the trick nicely, however the device I am using appears not to behave like that by default for some reason.....?
 
Upvote 0

warwound

Expert
Licensed User
Longtime User
Hi.

Try running this:

B4X:
'Activity module
Sub Process_Globals
   'These global variables will be declared once when the application starts.
   'These variables can be accessed from all modules.

End Sub

Sub Globals
   'These global variables will be redeclared each time the activity is created.
   'These variables can only be accessed from this module.
   Dim WebView1 As WebView
   Dim WebViewSettings1 As WebViewSettings
End Sub

Sub Activity_Create(FirstTime As Boolean)
   WebView1.Initialize("")
   Activity.AddView(WebView1, 0, 0, 100%x, 100%y)
   WebView1.LoadUrl("http://google.co.uk/")
   Log(WebViewSettings1.getCacheMode(WebView1))
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

The output from the log is:
LOAD_DEFAULT

The documentation doesn't seem to mention exactly what each cache mode is, for 'LOAD_DEFAULT' it just states:

Default cache usage pattern

See WebSettings | Android Developers.

The documentation for a lot of these WebSettings is minimal and vague.
But it does state that 'LOAD_CACHE_ELSE_NETWORK' will Use cache if content is there, even if expired (eg, history nav) If it is not in the cache, load from network.

The important thing is that you are forcing the browser to reload cached content without re-downloading it.

Martin.
 
Upvote 0

RichardN

Well-Known Member
Licensed User
Longtime User
I have been experimenting with the various cache modes available. Whilst they are all you need..... they appear to create as many problems as they solve.

My app displays timestamped webpages that self-refresh with operational data every minute or so. That in itself is already a pain but leaving any mode exposed that utilises the cache first creates the risk of displaying old data..... However when changing orientation reading from the cache is without a doubt the most user-friendly display.

As Activity-create is always executed with Actiivity-resume on reorientation I am struggling to find a logical cache mode sequence that satisfies the need for a quick refresh without switching to a mode that presents old data thereafter. Any suggestions ?
 
Upvote 0

warwound

Expert
Licensed User
Longtime User
Hi.

If your app loads a page from the cache then does it still refresh itself when a refresh is due?

Or does loading a page from the cache prevent the auto-refresh from happening?

Do you have any control over the webpages and how they refresh themselves or are they entirely third party pages?

Do the webpages use javascript to auto-refresh or do they use a META tag?

Martin.
 
Upvote 0

RichardN

Well-Known Member
Licensed User
Longtime User
Hi Martin,

The pages are refreshed with:

<meta http-equiv="refresh" content="15">

On closer inspection using 'LOAD_CACHE_ELSE_NETWORK' overides the HTML refresh tag completely so is useless here.

After some trial and error I think I have arrived at the best compromise of cache employment. The reorienation cycle of 'Pause-Create-Resume' is in some ways slightly illogical because one has to detect the cycle of a 'Pause' in order to process the 'Resume' correctly as otherwise the ActivityCreate will contain inappropriate statements.

I ultimately did it like this:
B4X:
Sub Process_Globals
   Dim CurrentURL As String
   Dim Paused As Boolean    
End Sub

Sub Globals
   Dim WebView1 As WebView
   Dim Wvs As WebViewSettings 
End Sub

Sub Activity_Pause (UserClosed As Boolean)
   CurrentURL = WebView1.Url 
   Paused = True 
End Sub

Sub Activity_Resume

   If Paused Then
   
      Wvs.setCacheMode(WebView1,"LOAD_CACHE_ONLY")
      Webview1.LoadURL (CurrentURL)
      Wvs.setCacheMode(WebView1,"LOAD_NO_CACHE")
      
   Else
      
      Wvs.setCacheMode(WebView1,"LOAD_NO_CACHE")
      Webview1.LoadURL (CurrentURL)
   
   End If
   
   Paused = False
   
End Sub

Doing it this way seems to give the fastest screen refresh time on reorientation. I am not 100% happy as it is still not as fast as the device internet application so I wonder if I should go down the road of saving the HTML from the WebView to a process variable then reload the view on Resume?

Can I extract the HTML text from a WebView to a Process variable without having to define an HTTP object and effectively download the HTML twice ?
 
Upvote 0

RichardN

Well-Known Member
Licensed User
Longtime User
Firstly, buffering the HTML using B4A.Callsub is faster, but....

Unfortunately the HTML string returned is modified. I have a couple of locally stored HTML files for menus in the app. They start off as....

.....<table border="1" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="100%" height ="100%" id="AutoNumber1">.....

The HTML returned by the JS function has 'height ="100%"' removed and consequently the page appearance changes completely on reorientation.

I thought I could escape the double inverted commas \" within a string literal to replace them or try an octal code \042 instead but neither worked:

html= html.Replace("width=\"100%\"","width=\"100%\" height=\"100%\"")

But that does not seem to work ? Am I missing some Javascript subtlety here?
 
Upvote 0

NJDude

Expert
Licensed User
Longtime User
You can replace the double quotes (") with single quotes (') as delimiters, example:

From:
B4X:
.....<table border="1" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="100%" height ="100%" id="AutoNumber1">.....

To:
B4X:
.....<table border='1' cellpadding='0' cellspacing='0' style='border-collapse: collapse' bordercolor='#111111' width='100%' height ='100%' id='AutoNumber1'>.....
 
Last edited:
Upvote 0

warwound

Expert
Licensed User
Longtime User
The pages are refreshed with:

<meta http-equiv="refresh" content="15">

On closer inspection using 'LOAD_CACHE_ELSE_NETWORK' overides the HTML refresh tag completely so is useless here.

Look at this screengrab of the webviewCache.db database from an app that loads content from the internet.
I did a test and found that the WebView doesn't cache anything that's loaded with it's LoadHtml method - nothing is added to the webviewCache.db database.
I'd imagine(!) that the expires column in the cache table dictates when the browser should refresh a page so a page that is not cached is never going to be refreshed.
By the way, i tested with loading local HTML files - using LoadUrl("file:///android_asset/my_page.htm") - and the WebView does not cache these files either.

Unfortunately the HTML string returned is modified. I have a couple of locally stored HTML files for menus in the app. They start off as....

.....<table border="1" cellpadding="0" cellspacing="0" style="border-collapse: collapse" bordercolor="#111111" width="100%" height ="100%" id="AutoNumber1">.....

The HTML returned by the JS function has 'height ="100%"' removed and consequently the page appearance changes completely on reorientation.

I thought I could escape the double inverted commas \" within a string literal to replace them or try an octal code \042 instead but neither worked:

html= html.Replace("width=\"100%\"","width=\"100%\" height=\"100%\"")

But that does not seem to work ? Am I missing some Javascript subtlety here?

No.

The HTML table element has no height attribute.
Invalid HTML is not rendered by the browser so isn't part of the rendered page.
The javascript gets the rendered page HTML and not the original page source.

Are you saying that the WebView renders a table with 100% height when the page is loaded from the server but NOT when it renders HTML passed to it's LoadHtml methods?

An idea i have though...

In the WebView PageFinished Sub query the WebView cache database for the filename of the page that has just been loaded.
Copy that file to File.DirInternalCache and then after an orientation change load that copy of the cached file using LoadUrl().
OR (a faster method maybe?)
Get the filename of the cached file and load it into a process global String variable.
On orientation change load that String using LoadHtml().

Unfortunately neither of these two methods will cause the WebView to cache the reloaded page so your meta refresh isn't going to work.

What you could try is the javascript location.reload() method after reloading the page on orientation change.

https://developer.mozilla.org/en/DOM/window.location

The reload method accepts a single optional Boolean parameter which if True forces a page reload from the server and not the cache.

But i think you'd not want to request a new page each time orientation changes?

Do you have any control over the web pages you are loading?
The pages are presumably created on the fly - or updated every 15 seconds by a script?
Could they be changed so that they use javascript to reload every 15 seconds?
If so then that javascript unlike the meta refresh tag will still work when the page is reloaded on orientation change...
(But not refresh in any browsers that have javascript disabled).

If you have control over the page creation you could simplify things a lot by creating a web service script that returns latest data only (no HTML) to your application in JSON or XML format.
You application (in B4A code not in javascript) could query the web service for updates every 15 seconds and create the HTML to be rendered on the fly - passing it to the WebView LoadHtml method.
As long as your app caches the last received data and keeps a timestamp of the last update then, on orientation change, you can restore the last data and also know when the next update is due.

Martin.
 
Upvote 0

RichardN

Well-Known Member
Licensed User
Longtime User
As reloading pages from the cache was always the second choice for speed I have persevered with this and made some discoveries.

- The original HTML menus I created with MS Front Page worked OK on first load but lost vertical formating when saved to memory with....

B4X:
Sub Process_Globals

   Dim CurrentURL As String   
   Dim htmlBuffer As String 
   Dim Paused As Boolean 
   
End Sub

Sub WebView1_PageFinished (Url As String)   
   
   Dim jsStatement As String
   jsStatement = "B4A.CallSub('processHTML', document.documentElement.outerHTML)"
   myInterface.execJS (WebView1,jsStatement)
   
End Sub

Sub processHTML(html As String)   

   htmlBuffer = html

End Sub

Sub Activity_Pause (UserClosed As Boolean)

   Paused = True 
      
End Sub

Sub Activity_Resume

   If Paused Then   
      WebView1.LoadHtml(htmlBuffer)
   Else
      Webview1.LoadUrl(CurrentURL)
   End If

   Paused = False
   
End Sub


On each occasion the HTML was returned without the <height = "100%"> in the table definition. So I tried defining height attributes to each row in the menu table. This time those tags were correctly returned by the JS but when loaded from the process variable on Activity_Resume they were ignored by the WebView.

However!

B4X:
htmlBuffer = html.Replace(Chr(34),Chr(39))
WebView1.LoadHtml(htmlBuffer)

If you replace all the double quotes in the returned HTML with single quotes and then reload into the WebView... all is displayed perfectly! This is now working at the speed I would expect from a native App.

I can only conclude that the statement WebView1.LoadHtml() ignores <table> and <tr> height tags if they are included in double quotes... but when single quotes are used everything is OK.

.......Go figure!



...Edited to add: If the page contains a META 'refresh' tag and the page was loaded from WebView1.LoadHTML(ProcessVariable) then the view throws a run-time error on refresh. It appears to be looking for a file. 'Cannot find file://..." Back to the drawing board!
 
Last edited:
Upvote 0
Top