Search code examples
androidservicecpuwakelock

Stop CPU from sleeping when screen is off


I have an app that runs a webview as a service so the audio can contiune to play while the screen is locked. The app works great with audio streams such as podcasts. But I also wanted it to work with flash video. I am able to load the flash video stream in the webview and have it play smooth and steady but once the screen goes off or is locked the audio becomes choppy. The behavior is the same on 3g and WiFi. I tried to use this posts suggestion of using a wake lock with:

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Tag"); 
wl.acquire();
//do what you need to do
wl.release(); 

However this has no effect. I'm not sure where exactly in my code to put it but I've placed it in the oncreate for the service, which had no effect, and I placed it in the oncreate for my main activity with the same results.

However later in the post the person asking the question said that WIFI_MODE_FULL_HIGH_PERF was able to fix the problem. But as I said I tested it on 3g and the audio stuttered when the screen was turned off.

Any ideas how I can stop this behavior?

Also I know this is a CPU intensive and a battery hog app but I'm just developing it for personal use.

This is my full code for the service:

public class MyService extends Service {
    private NotificationManager nm;
    private static boolean isRunning = false;

    ArrayList<Messenger> mClients = new ArrayList<Messenger>(); // Keeps track of all current registered clients.
    int mValue = 0; // Holds last value set by a client.
    static final int MSG_REGISTER_CLIENT = 1;
    static final int MSG_UNREGISTER_CLIENT = 2;
    static final int MSG_SET_INT_VALUE = 3;
    static final int MSG_SET_STRING_VALUE = 4;
    PowerManager.WakeLock wl;


    @Override
    public IBinder onBind(Intent intent) {
        wl.acquire();
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i("MyService", "Service Started.");
        showNotification();
        isRunning = true;
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 
        wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Tag");
    }
    private void showNotification() {
        nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        // In this sample, we'll use the same text for the ticker and the expanded notification
        CharSequence text = getText(R.string.service_started);
        // Set the icon, scrolling text and timestamp
        Notification notification = new Notification(R.drawable.ic_launcher, text, System.currentTimeMillis());
        notification.flags = Notification.FLAG_ONGOING_EVENT;
        // The PendingIntent to launch our activity if the user selects this notification
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0);
        // Set the info for the views that show in the notification panel.
        notification.setLatestEventInfo(this, getText(R.string.service_label), text, contentIntent);
        // Send the notification.
        // We use a layout id because it is a unique number.  We use it later to cancel.
        nm.notify(R.string.service_started, notification);
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("MyService", "Received start id " + startId + ": " + intent);
        return START_STICKY; // run until explicitly stopped.
    }

    public static boolean isRunning()
    {
        return isRunning;
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        nm.cancelAll();
        wl.release();
        nm.cancel(R.string.service_started); // Cancel the persistent notification.
        Log.i("MyService", "Service Stopped.");
        isRunning = false;
    }
}

My main code:

public class MainActivity extends Activity {
    Button btnStart, btnStop, btnBind, btnUnbind, btnUpby1, btnUpby10;
    Messenger mService = null;
    boolean mIsBound;
    WebView mWebView;

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            mService = new Messenger(service);
            try {
                Message msg = Message.obtain(null, MyService.MSG_REGISTER_CLIENT);
                     mService.send(msg);
            } catch (RemoteException e) {
                // In this case the service has crashed before we could even do anything with it
            }
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been unexpectedly disconnected - process crashed.
            mService = null;
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btnStart = (Button)findViewById(R.id.btnStart);
        btnStop = (Button)findViewById(R.id.btnStop);
        btnBind = (Button)findViewById(R.id.btnBind);
        btnUnbind = (Button)findViewById(R.id.btnUnbind);

        btnStart.setOnClickListener(btnStartListener);
        btnStop.setOnClickListener(btnStopListener);
        btnBind.setOnClickListener(btnBindListener);
        CheckIfServiceIsRunning();



        //webview
        mWebView = (WebView) findViewById(R.id.webview);
        mWebView.getSettings().setJavaScriptEnabled(true);

        mWebView.getSettings().setPluginsEnabled(true);
        mWebView.loadUrl(url);
        mWebView.setWebViewClient(new HelloWebViewClient());


    }


    private void CheckIfServiceIsRunning() {
        //If the service is running when the activity starts, we want to automatically bind to it.
        if (MyService.isRunning()) {
            doBindService();
        }
    }

    private OnClickListener btnStartListener = new OnClickListener() {
        public void onClick(View v){
            startService(new Intent(MainActivity.this, MyService.class));
        }
    };
    private OnClickListener btnStopListener = new OnClickListener() {
        public void onClick(View v){
            doUnbindService();
            stopService(new Intent(MainActivity.this, MyService.class));
        }
    };
    private OnClickListener btnBindListener = new OnClickListener() {
        public void onClick(View v){

            doBindService();
        }
    };
    private OnClickListener btnUnbindListener = new OnClickListener() {
        public void onClick(View v){

            doUnbindService();
        }
    };

    void doBindService() {
        bindService(new Intent(this, MyService.class), mConnection, Context.BIND_AUTO_CREATE);
        mIsBound = true;
    }
    void doUnbindService() {
        if (mIsBound) {
            // If we have received the service, and hence registered with it, then now is the time to unregister.
            if (mService != null) {
                try {
                    Message msg = Message.obtain(null, MyService.MSG_UNREGISTER_CLIENT);
                    mService.send(msg);
                } catch (RemoteException e) {
                    // There is nothing special we need to do if the service has crashed.
                }
            }
            // Detach our existing connection.
            unbindService(mConnection);
            mIsBound = false;
        }
    }

    private class HelloWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
            mWebView.goBack();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            doUnbindService();
        } catch (Throwable t) {
            Log.e("MainActivity", "Failed to unbind from the service", t);
        }
    }


     @Override
        protected void onResume() { 
            super.onResume();

     }
}

Solution

  • From your description it sounds like you are releasing the WakeLock too soon. If you are just putting that block of code in the onCreate for the Service then you really aren't getting a WakeLock while your streaming code is running. You must have the lock acquired for the duration of the background work.

    The pattern I've used for a service like this is:

    1. Create the WakeLock in onCreate (but do not acquire it)
    2. In onDestroy if the WakeLock is held release it. (mostly for safety)
    3. When starting the actual background work, either in onBind or onStartCommand, acquire the lock.
    4. When done with the background work, release the wake lock.

    The problem may be specifically with the way a WebView works. That said since this isn't production code, you could try simply 'leaking' the wake lock by removing the release just to see if that helps. Just create and acquire the lock at the start of your activity and not bother with the Service.

    The Service isn't really giving you much. The way your code is currently structured you are still going to have problems. Your Activity can be still deleted when in the background even if you have a Service, simply having a service won't stop this. Further because the WebView is a View it really requires an Activity and view hierarchy.