I'm searching for a while how to run my WebView forever. When the android function onPause() / onResume() is called. My WebView starts again.
I want the WebView to go on. Maybe the best to explane by an simple HTML example:
<html><head><title>Webview test Android</title></head>
<body>
<h1><div id="counter"></div></h1>
<script>
var counter = 0;
document.getElementById("counter").innerHTML = counter;
setTimeout(function(){ count(); }, 1000);
function count() {
counter++;
document.getElementById("counter").innerHTML = counter;
console.log("counter: " + counter);
setTimeout(function(){ count(); }, 1000);
}
</script>
</body>
</html>
What I want in the app behaviour is:
> Open the app
+ Html page starts counting
> Press the home button / multitask button
+ Html page is still counting (or doing other stuff)
> Get back to the app
+ Html page is still counting and dont reload.
First I uses my own code for WebView. But with a lot of android versions, I used a chromium-webview example from github
I looked into android WebView methods, but I cant figure a solution. I tried also mWebView.onPause(), mWebView.onResume()... With no results. Maybe someone can bring me in the right direction.
This is are the main functions:
private WebView mWebView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v("main"," create ");
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.activity_main_webview);
mWebView.addJavascriptInterface(new NotificationBindObject(getApplicationContext()), "NotificationBind");
mWebView.getSettings().setDomStorageEnabled(true); // use localstorage
setUpWebViewDefaults(mWebView);
if (savedInstanceState != null) {
mWebView.restoreState(savedInstanceState);
}
if(mWebView.getUrl() == null) {
mWebView.loadUrl("file:///android_asset/www/index.html");
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the WebView state (including history stack)
mWebView.saveState(savedInstanceState);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
Update
Almost there... I made a Service that runs the WebView.
public class TestService extends Service {
private static WebView w;
private static MainActivity ma;
#Override
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
}
public static void setMain(MainActivity a) {
ma = a;
Log.e("main", " setData " );
setView();
}
private static void setView() {
w = (WebView) ma.findViewById(R.id.webView);
Log.e("main", " setData ");
w.getSettings().setDomStorageEnabled(true); // use localstorage
w.getSettings().setJavaScriptEnabled(true);
//ma.setUpWebViewDefaults(w);
w.loadUrl("file:///android_asset/www/index.html");
}
}
MainActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v("main"," create ");
if (savedInstanceState == null) {
setContentView(R.layout.activity_main);
Intent i = new Intent(MainActivity.this, TestService.class);
TestService.setMain(this);
MainActivity.this.startService(i);
} else {
...
}
...
}
Now when I see the console.log("counter" + counter); logging in Eclipse. The only problem is that the WebView in the layout is gone now when I'm back...
Related
I have an android application in which I have a webview. How can I when I press the back button on android I can delete a character in the text area of webview?
package com.example.nqc.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
public class MainActivity extends AppCompatActivity {
private WebView mWebView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = findViewById(R.id.mWebView);
mWebView.loadUrl("https://www.google.com");
}
#Override
public void onBackPressed() {
//delete a character
Log.d("Debug", "Delete text on TextArea");
}
}
Please see the picture below if you do not understand what I said.
Before press back
After press back
There is no guarantee that there will be only one Form in the page the user has loaded.
However, if it's for the Google Search Form in particular, you can then execute a JavaScript to get a Form in the Google.com page that has id=tsf (which is the unique id of the search form in Google.com).
Nevertheless, you mentioned in your comment:
This will happen to all other pages
Unless you know the id of the form in the page (which is practically impossible for every page in WWW that contains more than one Form).
You can loop through all Forms and remove the last char of the text in the Form's input of type text.
Example
First create a method to run the JS script (note that there are different ways to execute a JS in WebView in Android, for more details in case the following method did not work for you, look here):
public void run(final String script) {
mWebView.post(new Runnable() {
#Override
public void run() {
mWebView.loadUrl("javascript:" + script);
}
});
}
Then create a method that returns a JS String to:
Get all Forms in the page.
Cycle through them all.
Cycle through the elements of each Form..
Set its value to (its old value - last char).
private String deleteLastCharScript() {
return "var listOfForms = document.forms;\n" +
"for(var i = 0; i < listOfForms.length; i++) {\n" +
" var elements = listOfForms[i].elements;\n" +
" for(var j = 0 ; j < elements.length ; j++){\n" +
" var item = elements.item(j);\n" +
" item.value = item.value.substring(0, item.value.length - 1);\n" +
" }\n" +
"};";
}
You can run that script in the onBackPressed() method like this:
#Override
public void onBackPressed() {
run(deleteLastCharScript());
}
This is MCVE:
Add to Manifest:
<uses-permission android:name="android.permission.INTERNET" />
Create Simple WebView Layout:
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/webView1"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
Implementation in Activity:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.WebView;
public class MainActivity extends AppCompatActivity {
private WebView mWebView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.webview);
mWebView = findViewById(R.id.webView1);
mWebView.getSettings().setJavaScriptEnabled(true); // enable JS
mWebView.loadUrl("https://www.google.com");
}
/**
* Execute JS String Asynchronously
* #param script
*/
public void run(final String script) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
// run JavaScript asynchronously
// it works on KitKat onwards
mWebView.evaluateJavascript(script, null);
}else {
// use different thread to run JavaScript asynchronously
// because evaluateJavascript doesn't work on versions before KitKat
new Thread(new Runnable() {
#Override
public void run() {
mWebView.post(new Runnable() {
#Override
public void run() {
mWebView.loadUrl("javascript:" + script);
}
});
}
}).start();
}
}
/**
* This JS Script tp Loop through all
* inputs in all forms in the page
* and remove last char from each
* #return
*/
private String deleteLastCharScript() {
return "var listOfForms = document.forms;\n" +
"for(var i = 0; i < listOfForms.length; i++) {\n" +
" var elements = listOfForms[i].elements;\n" +
" for(var j = 0 ; j < elements.length ; j++){\n" +
" var item = elements.item(j);\n" +
" item.value = item.value.substring(0, item.value.length - 1);\n" +
" }\n" +
"};";
}
#Override
public void onBackPressed() {
run(deleteLastCharScript());
}
}
Result
i have a webview that read HTML from SD-card and show it . when user open activity for first time, HTML file load correctly . but in some devices (such as Samsung galaxy series) if close activity and open it again, webview not show anything and is empty.
public class page extends Activity {
private static WebView web;
private int path;
#SuppressLint("SetJavaScriptEnabled")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
path = bundle.getInt("path");
web = (WebView) findViewById(R.id.webview);
web.setBackgroundColor(Color.TRANSPARENT);
web.getSettings().setJavaScriptEnabled(true);
web.setWebChromeClient(new WebChromeClient());
}
#Override
protected void onResume() {
super.onResume();
loadPage();
}
#Override
protected void onPause() {
super.onPause();
resetWebView();
}
// becuase my webview play audio and if close activity sound also playing
private void resetWebView(){
if (web != null){
web.loadUrl("about:blank");
web.destroy();
web.destroyDrawingCache();
}
}
private void loadPage(){
String DIR = Environment.getExternalStorageDirectory().getAbsolutePath()+"/myApp";
if(path != null && web != null){
switch (path) {
case 3: {
// something like this
// /storage/emulated/0/myApp/1/page_three.html/
File file = new File(DIR+path+"page_three.html/");
web.loadUrl("file://"+file);
}
break;
case 2: {
File file = new File(G.DIR_APP+path+"page_two.html/");
web.loadUrl("file://"+file);
}
break;
case 1: {
File file = new File(G.DIR_APP+path+"page_one.html/");
web.loadUrl("file://"+file);
}
break;
case 0: {
File file = new File(G.DIR_APP+path+"welcome.html/");
web.loadUrl("file://"+file);
}
break;
default:
// do nothing
}
}
}
I had the same issue. This is for API 11 and greater. Adding webview.onResume() inside the onResume() function of fragment solved it for me.
So in your case:
public void onResume() {
super.onResume();
web.onResume();
}
If the activity is destroyed while paused, the Android system may re-create it when the user returns to it later. To ensure that the same data that was originally used to intialise the activity is restored, you need to override onSaveInstanceState() and onRestoreInstanceState(). I recommend this pattern - this will ensure the correct data is loaded for initial creation, resume after pause, recreation and resume after being destroyed, and when sent a new intent to load new data.
static final String ARG_PATH = "path";
protected Bundle bundle = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bundle = getIntent().getExtras();
// perform other create-time initialisation here
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
bundle = intent.getExtras();
}
#Override
protected void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
bundle = savedInstanceState;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(ARG_PATH, path);
}
#Override
protected void onResume() {
super.onResume();
if(bundle != null) {
path = bundle.getInt(ARG_PATH);
bundle = null;
loadPage();
} else
web.onResume();
// if bundle was already null, we were just resumed after pause, not destroyed so no reload is required.
}
#Override
protected void onPause() {
web.onPause(); // pause any video etc.
super.onPause();
}
when you leave activity, finish the activity, so next time you come, a method onCreate(Bundle savedInstanceState) must be called again.
i add the following code to webview and problem solved
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
I'm trying to make a splash screen for my Android app, where the login page just starts the Splash activity, and then all the login processing stuff is done there. It then returns a boolean whether login succeeded or not. But the login processing completes before the XML layout content loads. How can this be fixed?
This is my splash screen activity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras();
if(extras !=null)
{
url = extras.getString("url");
}
setContentView(R.layout.splashscreen);
url += "login/?userName=" + Login.loginName + "&password=" + Login.password;
DomLoginParser parser = new DomLoginParser(url);
if(parser.parse())
{
Login.loginSuccessful = true;
}
else
{
Login.loginSuccessful = false;
}
finish();
}
Move the login out of the onCreate call by using AsyncTask to perform the login.
What you are doing is finishing the activity before giving it a chance to show the contents.
The activity is showing the contentView only after the onCreate call is finished...
Don't know what DomLoginParser do, and thinking that the Login class is static, but why do you call the finish() method inside the onCreate() ?
That's the problem, you kill the activity immediately!
Remove the finish() invocation.
If
DomLoginParser parser = new DomLoginParser(url);
if(parser.parse())
{
Login.loginSuccessful = true;
}
else
{
Login.loginSuccessful = false;
}
this is all you do about login, and want to have the splash screen visible for some time, call a Thread.sleep() (but be aware that it can produce Application Not Responding if sleeping for too much time), or simply create a separate process (Asynctask or threadhandler associated with another thread that say to your splash screen to finish).
Try something like this:
private class LoginChecker extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... args) {
DomLoginParser parser = new DomLoginParser(url);
if(parser.parse()) {
Login.loginSuccessful = true;
} else {
Login.loginSuccessful = false;
}
return null;
}
protected void onPostExecute(Long result) {
// finish the activity
}
}
To call this, update your onCreate method to something along the lines of:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras();
if(extras !=null) {
url = extras.getString("url");
}
setContentView(R.layout.splashscreen);
url += "login/?userName=" + Login.loginName + "&password=" + Login.password;
new LoginChecker.execute();
}
I got a VERY STRANGE situation...(to me)
For example, 2 objects,
1 is an activity member boolean called isInPage,
2 is a static bitmap object called bmpPhoto.
When I get into my own activity called FacebookShareActivity
isInPage will be true until I quit this activity,
bmpPhoto will be given a picture.
After onCreare() and onResume(), there is no any code running, until user click some GUI.
What I did is close screen by press hardware power button, and maybe wait 5 or 10 minutes.
OK, now I press porwe again to wake phone up, unlock screen,
and my FacebookShareActivity goes back to front.
And I click my GUI button to check variable value via Logcat, it says:
isInPage=false;
And I forget bmpPhoto's value, but on my GUI, the photo just gone,
not displayed anymore...
How is this happen ?
And it just not happen every time after I do that......
What if I override onSaveInstanceState(Bundle savedInstanceState) and
onRestoreInstanceState(Bundle savedInstanceState) ?
Will it help ?
And what about the bitmap object ?
Still don't know how is that happen...
Did I miss something ?
I really need your help, please everyone~
Following is part of my code, quite long...
The "isPageRunning" and "bmp" changed sometime when back from desktop, but not everytime.
public class FacebookShareActivity extends Activity
{
private Bundle b=null;
private Bitmap bmp=null;
private boolean isFacebookWorking=false;
private boolean isPageRunning=true; //This value sometime changed when back from desktop, but not every time
protected void onCreate(Bundle savedInstanceState)
{
Log.i(Constants.TAG, "ON Facebook Share create......");
super.onCreate(savedInstanceState);
setContentView(R.layout.facebook_share);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
private void initUI()
{
btnBack=(Button)findViewById(R.id.btnBack);
btnBack.setOnClickListener(new ButtonClickHandler());
formImage=(RelativeLayout)findViewById(R.id.form_image);
formImage.setDrawingCacheEnabled(true);
btnShare=(Button)findViewById(R.id.btnShare);
btnShare.setOnClickListener(new ButtonClickHandler());
txtIntroText=(TextView)findViewById(R.id.txtIntroText);
txtIntroText.setOnClickListener(new ButtonClickHandler());
txtIntroText.setText(getUploadInImageText());
photo=(ImageView)findViewById(R.id.photo);
bmp=Constants.PROFILE.getName().getPhoto();
if(bmp!=null)
{photo.setImageBitmap(bmp);} //bmp wouldn't be null, it filled by some other activity before
}
#Override
protected void onResume()
{
super.onResume();
Log.i(Constants.TAG, "Trying to set UI on resume...");
b=getIntent().getExtras();
// ...
// ... Get some String value passed from prev activity
facebook=new Facebook("123456789012345"); //Test
asyncFacebook=new AsyncFacebookRunner(facebook);
initUI();
System.gc();
}
#Override
public void onBackPressed()
{
Log.d(Constants.TAG, "Activity receive back key...");
lockButtons(false);
return;
}
private void lockButtons(boolean b)
{
if(isPageRunning)
{
btnBack.setClickable(!b);
btnShare.setClickable(!b);
}
}
private class DelayReleaseKey implements Runnable
{
public void run()
{
try{Thread.sleep(10000);}
catch(InterruptedException ie){}
handler.sendEmptyMessage(0);
}
}
private class ButtonClickHandler implements OnClickListener
{
public void onClick(View v)
{
if(v==btnBack)
{
if(isFacebookWorking)
{ShowAlertDialog(Constants.MESSAGE_FACEBOOK_WORK);}
else
{
lockButtons(true);
formImage=null;
photo=null;
b=null;
facebook=null;
isPageRunning=false;
Intent intent=new Intent(FacebookShareActivity.this, PracticeListActivity.class);
startActivity(intent);
FacebookShareActivity.this.finish();
overridePendingTransition(android.R.anim.slide_in_left,android.R.anim.slide_out_right);
}
}
if(v==btnShare)
{
lockButtons(true);
facebookLogin();
}
}
}
}
Now I know i must override onSaveInstanceState, onRestoreInstanceState.
They can help me to save variable like String, int, boolean...
What about Bitmap ?
And what if my variable is static ?
Now try again.
public class FacebookShareActivity extends Activity
{
private Bundle b=null;
private static Bitmap bmp=null;
private static boolean isFacebookWorking=false;
private static boolean isPageRunning=true; //This value sometime changed when back from desktop, but not every time
protected void onCreate(Bundle savedInstanceState)
{
Log.i(Constants.TAG, "ON Facebook Share create......");
super.onCreate(savedInstanceState);
setContentView(R.layout.facebook_share);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
}
private void initUI()
{
btnBack=(Button)findViewById(R.id.btnBack);
btnBack.setOnClickListener(new ButtonClickHandler());
formImage=(RelativeLayout)findViewById(R.id.form_image);
formImage.setDrawingCacheEnabled(true);
btnShare=(Button)findViewById(R.id.btnShare);
btnShare.setOnClickListener(new ButtonClickHandler());
txtIntroText=(TextView)findViewById(R.id.txtIntroText);
txtIntroText.setOnClickListener(new ButtonClickHandler());
txtIntroText.setText(getUploadInImageText());
photo=(ImageView)findViewById(R.id.photo);
bmp=Constants.PROFILE.getName().getPhoto();
if(bmp!=null)
{photo.setImageBitmap(bmp);} //bmp wouldn't be null, it filled by some other activity before
}
#Override
protected void onResume()
{
super.onResume();
isPageRunning = true;
Log.i(Constants.TAG, "Trying to set UI on resume...");
b=getIntent().getExtras();
// ...
// ... Get some String value passed from prev activity
facebook=new Facebook("123456789012345"); //Test
asyncFacebook=new AsyncFacebookRunner(facebook);
initUI();
System.gc();
}
#Override
protected void onPause()
{
isPageRunning = false;
}
#Override
public void onBackPressed()
{
Log.d(Constants.TAG, "Activity receive back key...");
lockButtons(false);
return;
}
private void lockButtons(boolean b)
{
if(isPageRunning)
{
btnBack.setClickable(!b);
btnShare.setClickable(!b);
}
}
private class DelayReleaseKey implements Runnable
{
public void run()
{
try{Thread.sleep(10000);}
catch(InterruptedException ie){}
handler.sendEmptyMessage(0);
}
}
private class ButtonClickHandler implements OnClickListener
{
public void onClick(View v)
{
if(v==btnBack)
{
if(isFacebookWorking)
{ShowAlertDialog(Constants.MESSAGE_FACEBOOK_WORK);}
else
{
lockButtons(true);
formImage=null;
photo=null;
b=null;
facebook=null;
isPageRunning=false;
Intent intent=new Intent(FacebookShareActivity.this, PracticeListActivity.class);
startActivity(intent);
FacebookShareActivity.this.finish();
overridePendingTransition(android.R.anim.slide_in_left,android.R.anim.slide_out_right);
}
}
if(v==btnShare)
{
lockButtons(true);
facebookLogin();
}
}
}
}
For primitive values, you should use onSaveInstanceState. For restoring you can use onRestoreInstanceState or you can some code in onCreate like this:
if(savedInstanceState != null) {
// restore old state
} else {
// a fresh start
}
Now for restoring objects like Bitmap if they are not expensive to create and doesn't make your UI sluggish, create them again on restore. If you do not want to that then use onRetainNonConfigurationInstance and code will look like this:
#Override
public Object onRetainNonConfigurationInstance () {
return bmp;
}
#Override
public void onCreate() {
if
bmp = (Bitmap)getLastNonConfigurationInstance();
}
WARNING: This api is deprecate, you might use it on old platforms. I put it here for illustration purpose. The new way to do this is more involving.
Here is detailed ref:
getLastNonConfigurationInstance
onRetainNonConfigurationInstance
I have looked around on the API and through a few questions on here, and I think I am on the right path. My app is based on a webView object and the initial load has quite a few cached pages so I want progressDialog on the initial start up instead of the blank black screen. Right now the app just crashes but I believe it is because I am creating and calling the AsyncTask object in the wrong place. Right now it is being called in the onCreate() method. I’m not new to Java but I am new to Android and this idea of not working with a main() function is confusing to me.
So where should I call the execute() function if I only want the ProgressDialog shown on the initial launch? And is my AsyncTask object even set up correctly?
public class site extends Activity {
private WebView engine;
private String urlSave;
private WebViewClient yourWebClient;
private ProgressDialog initLoadDialog;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash);
yourWebClient = new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.contains("tel:") == true) {
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
startActivity(intent);
}
else if(url.contains(“blah") == true && url.contains(“blah2") == false) {
view.loadUrl(url);
}
else if(url.contains(“blah3") == true) {
double[] loc = getGPS();
url += "&cLat=" + loc[0] + "&cLong=" + loc[1];
view.loadUrl(url);
}
else {
/*Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("url"));
startActivity(browserIntent);*/
}
return true;
}
};
}
#Override
public void onStart() {
progressSetup();
setContentView(R.layout.main);
}
public void progressSetup () {
initLoadDialog = new ProgressDialog(site.this);
initLoadDialog.setMessage("A message");
initLoadDialog.setIndeterminate(false);
initLoadDialog.setMax(100);
initLoadDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
urlLoad loading = new urlLoad();
loading.execute();
}
private class urlLoad extends AsyncTask<String, Integer, String>{
#Override
protected String doInBackground(String... url) {
try {
engine = (WebView) findViewById(R.id.web_engine);
engine.getSettings().setJavaScriptEnabled(true);
engine.getSettings().setBuiltInZoomControls(true);
engine.getSettings().setSupportZoom(true);
engine.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
engine.getSettings().setGeolocationEnabled(true);
engine.setWebViewClient(yourWebClient);
engine.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
engine.loadUrl(“albhal");
} catch (Exception e) {}
return null;
}
protected void onProgressUpdate(Integer... progress) {
initLoadDialog.setProgress(engine.getProgress());
}
}
}
Check your adb log, the error will pretty much explain to you what you didn't do right.
There's a lot of bad practice in your code. For example you call setContentView() in two Methods with different Layouts. The Flow of a android application is to call "onCreate", then "onStart". There is no reason to distinguish between those methods for you. Merge them and decide which layout to populate.
Also it is recommended to change the user-interface (this means also the dialogs) through the managing activity. In your case you are creating a ProgressDialog in the activity which then gets modified by the task. This is something you should avoid.