29

I am working on mobile application developed on cordova . I want to implement a background service that do some work like open socket connection syncronise local database with remote one and notify the users on new remote pushes etc . The point is I have this code implemented in javascript but I want execute it i background.

I searched internet for a cordova background service plugin.

I have read some topics about background service in android these are useful ones I found:

So I started writing cordova plugin (primarily on android) to execute the javascript code in background. I created a webview from the background service to execute the javascript from it. This works fine when I execute normal javascript but when it comes to cordova plugins js it fails for example the device device.uuid gives null.

This is the java service code:

      public void onStart(Intent intent, int startId) {
      Toast.makeText(this, "My Happy Service Started", Toast.LENGTH_LONG).show();
       
           createBackGroundView();
           super.onStart(intent,startId);
    }


      public void createBackGroundView(){

         
        WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
       LayoutParams params = new WindowManager.LayoutParams(
                   android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
                   android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
                   WindowManager.LayoutParams.TYPE_PHONE,
                   WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
                   PixelFormat.TRANSLUCENT
           );
         
        params.gravity = Gravity.TOP | Gravity.LEFT;
        params.x = 0;
        params.y = 0;
        params.width = 200;
        params.height = 200;
       
        LinearLayout view = new LinearLayout(this);
           
        view.setLayoutParams(new RelativeLayout.LayoutParams(
                    android.view.ViewGroup.LayoutParams.MATCH_PARENT, 
                    android.view.ViewGroup.LayoutParams.MATCH_PARENT
            ));
           
        WebView wv = new WebView(this);
        wv.setLayoutParams(new LinearLayout.LayoutParams(
                    android.view.ViewGroup.LayoutParams.MATCH_PARENT,
                    android.view.ViewGroup.LayoutParams.MATCH_PARENT
            ));     
        view.addView(wv);
        wv.getSettings().setJavaScriptEnabled(true);
        wv.setWebChromeClient(new WebChromeClient());
            wv.loadUrl("file:///android_asset/www/background.html");
        wv.setWebViewClient(new WebViewClient() {

            @Override
            public void onReceivedError(final WebView view, int errorCode,
                    String description, final String failingUrl) {
                Log.d("Error","loading web view");
                super.onReceivedError(view, errorCode, description, failingUrl);
            }
        });

        windowManager.addView(view, params);
  
     }



     

Update There is no error in the logcat. So I tried to write the device object on the screen and thats what I get :

  document.write(JSON.stringify(window.device))

And this is the result :

  { available : false, 
    plaform : null , 
    version : null , 
    uuid : null ,  
    cordova : null ,
    model : null 
   }

I tried to replace the standard webView with cordovaWebView But the same result is given.

       //WebView wv = new WebView(this);  Commented out
       CordovaWebView wv = new CordovaWebView(this);

  

Any help about this problem ?

Community
  • 1
  • 1
mehsen
  • 588
  • 2
  • 7
  • 18
  • did this help: http://stackoverflow.com/questions/21505369/phonegap-keep-running-on-android-after-onpause/21565628#21565628 – kirchberger Jul 08 '14 at 08:05
  • In my case the javascript is exected and keeps running but the problem is in the execution of **cordova plugin js** in background. – mehsen Jul 08 '14 at 09:06
  • can you post an error or a log – kirchberger Jul 08 '14 at 09:16
  • @kirchberger I updated my question look above I have no error but the device object is empty – mehsen Jul 10 '14 at 14:09
  • Did you look into using the Cordova WebView as I suggested in my answer? It seems to me that the .js for the device plugin just initializes an empty device object, then it calls out to the native part but this fails (silently) because a standard WebView does not have handlers for it. – Jan Misker Jul 18 '14 at 13:25
  • @Jan Misker Yes I have tried to replace the standared webview with CordovaWebView but It did not work . The same result is given – mehsen Jul 19 '14 at 09:25
  • It has been awhile. What did you decide to do? Did you abandon Cordova and learn native or did you find a solution for at least Android and iOS? – Roy Oliver Feb 23 '17 at 12:18
  • Currently I'm using jsevaluator in my cordova plugin when running headless. It has limitation of not being able to call any cordova plugin methods, just plain js. Do you think is it possible to initiate CordovaWebView in headless mode to execute js code, but also have possibility to call cordova plugins? – mauron85 Apr 14 '18 at 16:56

3 Answers3

5

You should use an embedded Cordova WebView, not a standard WebView. A standard WebView is not set up to handle Cordova plugins, and the device info is a plugin.

See the Cordova docs on embedding webviews.

Jan Misker
  • 2,129
  • 16
  • 26
  • The same result is given I will edit my question to see how I use cordovaWebView – mehsen Jul 19 '14 at 09:40
  • Did you follow all steps in the docs? Did you set a Threadpool for the plugins to run in? – Jan Misker Jul 19 '14 at 23:49
  • From what you wrote in your answer it seems you only use the CordovaWebView class, but in the Cordova docs (link in my answer) it is clear that more steps have to be taken in order to configure the CordovaWebView. It will be this configuration that hooks up the plugins (e.g. the device plugin that doesn't work right now). – Jan Misker Jul 31 '14 at 13:45
3

WebViews can not execute javascript from a background service.

I would recommend using native code instead. But if you must use javascript, i would try this library

https://code.google.com/p/jav8/

ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("jav8");

  try {
    engine.eval("print('Hello, world!')");
  } catch (ScriptException ex) {
      ex.printStackTrace();
  } 

First load the contens of your script into a string and then run engine.eval() method.

Example (Run "test.js" from assets):

AssetManager am = context.getAssets();
InputStream is = am.open("test.js");
BufferedReader r = new BufferedReader(new InputStreamReader(is));
StringBuilder total = new StringBuilder();
String line;
while ((line = r.readLine()) != null) {
    total.append(line);
}

ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("jav8");

  try {
    engine.eval(total.toString());
  } catch (ScriptException ex) {
      ex.printStackTrace();
  }

Notice! The eval function expects only a javascript function to be executed at a time and returns the value of this function.

Richard
  • 14,427
  • 9
  • 57
  • 85
  • I am using a webview instatiated in the service to execute my js and it is working , But fails when use javascript of cordova plugins – mehsen Jul 15 '14 at 11:00
  • Any idea about the issue ? – mehsen Jul 15 '14 at 15:16
  • @mehsen you cannot use Cordova plugins without initialization. When Cordova engine initializes it goes through an elaborate process to setup the plugins. You would need to repeat the whole process in the background service. – Nandeep Mali May 16 '16 at 14:40
  • Webview can execute js from a background service. I'm using evgenii/jsevaluator in https://github.com/mauron85/background-geolocation-android/tree/master/src/main/java/com/evgenii/jsevaluator. – mauron85 Apr 14 '18 at 16:53
2

To work with Cordova plugins in WebView as background service, i've created class that implements CordovaInterface. Here is an example

 private class CordovaBackground extends Activity implements CordovaInterface {
    private ArrayList pluginEntries = new ArrayList();
    private CordovaPreferences preferences;
    private Context context;
    private Whitelist internalWhitelist;
    private Whitelist externalWhitelist;
    private CordovaWebViewBackground webView;
    protected LinearLayout root;
    private WindowManager serviceWindowManager;
    private final ExecutorService threadPool = Executors.newCachedThreadPool();

    public CordovaBackground(Context context, WindowManager windowManager) {
        attachBaseContext(context);
        this.context = context;
        this.serviceWindowManager = windowManager;
    }

    private void loadConfig() {
        ConfigXmlParser parser = new ConfigXmlParser();
        parser.parse(this);
        preferences = parser.getPreferences();
        internalWhitelist = parser.getInternalWhitelist();
        externalWhitelist = parser.getExternalWhitelist();;
        ArrayList<PluginEntry> allPluginEntries = parser.getPluginEntries();
        String[] backgroundPluginNames = {"File"};//not all plugins you need in service, here is the list of them
        ArrayList<String> backgroundPlugins = new ArrayList<String>(
            Arrays.asList(backgroundPluginNames));
        for (PluginEntry pluginEntry : allPluginEntries) {
            if (backgroundPlugins.contains(pluginEntry.service)) {
                pluginEntries.add(pluginEntry);
            }
        }
    }

    public void loadUrl(String url) {
        init();
        webView.loadUrl(url);
    }

    public void init() {
        loadConfig();
        webView = new CordovaWebViewBackground(context);
        if (webView.pluginManager == null) {
            CordovaWebViewClient webClient = webView.makeWebViewClient(this);
            CordovaChromeClientBackground webChromeClient = webView.makeWebChromeClient(this);
            webView.init(this, webClient, webChromeClient,
                    pluginEntries, internalWhitelist, externalWhitelist, preferences);
        }
    }

    public WindowManager getWindowManager() {
        return serviceWindowManager;
    }

    @Override
    public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
    }

    @Override
    public void setActivityResultCallback(CordovaPlugin plugin) {
    }

    @Override
    public Activity getActivity() {
        return this;
    }

    @Override
    public Object onMessage(String id, Object data) {
        return null;
    }

    @Override
    public ExecutorService getThreadPool() {
        return threadPool;
    }

    @Override
    public Intent registerReceiver(android.content.BroadcastReceiver receiver, android.content.IntentFilter filter) {
        return  getIntent();
    }

    @Override
    public String getPackageName() {
        return context.getPackageName();
    }

}

To prevent errors while cordova initializing, i've overrode onJsAlert method. If you have a time, you may have found better way.

 private class CordovaWebViewBackground extends CordovaWebView {

    public CordovaWebViewBackground(Context context) {
        super(context);
    }

    public CordovaChromeClientBackground makeWebChromeClient(CordovaInterface cordova) {
        return new CordovaChromeClientBackground(cordova, this);
    }

}

private class CordovaChromeClientBackground extends CordovaChromeClient {

    public CordovaChromeClientBackground(CordovaInterface ctx, CordovaWebView app) {
        super(ctx, app);
    }

    public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
        //super.onJsAlert(view, url, message, result);
        return true;
    }

}

How to use:

WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
CordovaBackground cordovaBackground = new CordovaBackground(this, wm);
cordovaBackground.setIntent(intent);
String url = "file:///android_asset/www/test.html";
cordovaBackground.loadUrl(url);
CruorVult
  • 823
  • 1
  • 9
  • 17