diff options
| author | Android (Google) Code Review <android-gerrit@google.com> | 2009-08-24 17:01:50 -0700 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2009-08-24 17:01:50 -0700 |
| commit | cf3a08307d1599eaa91d7cc4e7c601e5fa13037f (patch) | |
| tree | e885507688b1bc361a23147179e798b81fb9c2e3 | |
| parent | ec9fe4672a46eb928ab710d8e3caf2ce046100d4 (diff) | |
| parent | f6f9f2d0256930ce0bb4913b2260b8480914edc2 (diff) | |
| download | frameworks_base-cf3a08307d1599eaa91d7cc4e7c601e5fa13037f.zip frameworks_base-cf3a08307d1599eaa91d7cc4e7c601e5fa13037f.tar.gz frameworks_base-cf3a08307d1599eaa91d7cc4e7c601e5fa13037f.tar.bz2 | |
Merge change 22400 into eclair
* changes:
Add more control over a service's start state.
| -rw-r--r-- | api/current.xml | 118 | ||||
| -rw-r--r-- | core/java/android/app/ActivityManagerNative.java | 11 | ||||
| -rw-r--r-- | core/java/android/app/ActivityThread.java | 19 | ||||
| -rw-r--r-- | core/java/android/app/ApplicationThreadNative.java | 20 | ||||
| -rw-r--r-- | core/java/android/app/IActivityManager.java | 3 | ||||
| -rw-r--r-- | core/java/android/app/IApplicationThread.java | 3 | ||||
| -rw-r--r-- | core/java/android/app/IntentService.java | 21 | ||||
| -rw-r--r-- | core/java/android/app/Service.java | 113 | ||||
| -rw-r--r-- | core/java/android/os/Build.java | 13 | ||||
| -rw-r--r-- | services/java/com/android/server/am/ActivityManagerService.java | 196 | ||||
| -rw-r--r-- | services/java/com/android/server/am/ServiceRecord.java | 68 |
11 files changed, 545 insertions, 40 deletions
diff --git a/api/current.xml b/api/current.xml index 7fad505..bb70f6f 100644 --- a/api/current.xml +++ b/api/current.xml @@ -21568,6 +21568,19 @@ <parameter name="intent" type="android.content.Intent"> </parameter> </method> +<method name="setIntentRedelivery" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="enabled" type="boolean"> +</parameter> +</method> </class> <class name="KeyguardManager" extends="java.lang.Object" @@ -23767,11 +23780,28 @@ synchronized="false" static="false" final="false" + deprecated="deprecated" + visibility="public" +> +<parameter name="intent" type="android.content.Intent"> +</parameter> +<parameter name="startId" type="int"> +</parameter> +</method> +<method name="onStartCommand" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" deprecated="not deprecated" visibility="public" > <parameter name="intent" type="android.content.Intent"> </parameter> +<parameter name="flags" type="int"> +</parameter> <parameter name="startId" type="int"> </parameter> </method> @@ -23866,6 +23896,83 @@ <parameter name="startId" type="int"> </parameter> </method> +<field name="START_CONTINUATION_MASK" + type="int" + transient="false" + volatile="false" + value="15" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="START_FLAG_REDELIVERY" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="START_FLAG_RETRY" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="START_NOT_STICKY" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="START_REDELIVER_INTENT" + type="int" + transient="false" + volatile="false" + value="3" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="START_STICKY" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="START_STICKY_COMPATIBILITY" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> </class> <class name="TabActivity" extends="android.app.ActivityGroup" @@ -95009,6 +95116,17 @@ visibility="public" > </field> +<field name="ECLAIR" + type="int" + transient="false" + volatile="false" + value="10000" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> </class> <class name="Bundle" extends="java.lang.Object" diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 4dc23c0..d14ec15 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -608,7 +608,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case SERVICE_DONE_EXECUTING_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); - serviceDoneExecuting(token); + int type = data.readInt(); + int startId = data.readInt(); + int res = data.readInt(); + serviceDoneExecuting(token, type, startId, res); reply.writeNoException(); return true; } @@ -1746,11 +1749,15 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } - public void serviceDoneExecuting(IBinder token) throws RemoteException { + public void serviceDoneExecuting(IBinder token, int type, int startId, + int res) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); + data.writeInt(type); + data.writeInt(startId); + data.writeInt(res); mRemote.transact(SERVICE_DONE_EXECUTING_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); reply.readException(); data.recycle(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e045105..1e915b4 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1215,6 +1215,7 @@ public final class ActivityThread { private static final class ServiceArgsData { IBinder token; int startId; + int flags; Intent args; public String toString() { return "ServiceArgsData{token=" + token + " startId=" + startId @@ -1417,10 +1418,11 @@ public final class ActivityThread { } public final void scheduleServiceArgs(IBinder token, int startId, - Intent args) { + int flags ,Intent args) { ServiceArgsData s = new ServiceArgsData(); s.token = token; s.startId = startId; + s.flags = flags; s.args = args; queueOrSendMessage(H.SERVICE_ARGS, s); @@ -2684,7 +2686,8 @@ public final class ActivityThread { service.onCreate(); mServices.put(data.token, service); try { - ActivityManagerNative.getDefault().serviceDoneExecuting(data.token); + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token, 0, 0, 0); } catch (RemoteException e) { // nothing to do. } @@ -2710,7 +2713,7 @@ public final class ActivityThread { } else { s.onRebind(data.intent); ActivityManagerNative.getDefault().serviceDoneExecuting( - data.token); + data.token, 0, 0, 0); } } catch (RemoteException ex) { } @@ -2736,7 +2739,7 @@ public final class ActivityThread { data.token, data.intent, doRebind); } else { ActivityManagerNative.getDefault().serviceDoneExecuting( - data.token); + data.token, 0, 0, 0); } } catch (RemoteException ex) { } @@ -2773,9 +2776,10 @@ public final class ActivityThread { if (data.args != null) { data.args.setExtrasClassLoader(s.getClassLoader()); } - s.onStart(data.args, data.startId); + int res = s.onStartCommand(data.args, data.flags, data.startId); try { - ActivityManagerNative.getDefault().serviceDoneExecuting(data.token); + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token, 1, data.startId, res); } catch (RemoteException e) { // nothing to do. } @@ -2801,7 +2805,8 @@ public final class ActivityThread { ((ApplicationContext) context).scheduleFinalCleanup(who, "Service"); } try { - ActivityManagerNative.getDefault().serviceDoneExecuting(token); + ActivityManagerNative.getDefault().serviceDoneExecuting( + token, 0, 0, 0); } catch (RemoteException e) { // nothing to do. } diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index 5335239..ad64465 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -206,8 +206,14 @@ public abstract class ApplicationThreadNative extends Binder data.enforceInterface(IApplicationThread.descriptor); IBinder token = data.readStrongBinder(); int startId = data.readInt(); - Intent args = Intent.CREATOR.createFromParcel(data); - scheduleServiceArgs(token, startId, args); + int fl = data.readInt(); + Intent args; + if (data.readInt() != 0) { + args = Intent.CREATOR.createFromParcel(data); + } else { + args = null; + } + scheduleServiceArgs(token, startId, fl, args); return true; } @@ -573,12 +579,18 @@ class ApplicationThreadProxy implements IApplicationThread { } public final void scheduleServiceArgs(IBinder token, int startId, - Intent args) throws RemoteException { + int flags, Intent args) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); data.writeInt(startId); - args.writeToParcel(data, 0); + data.writeInt(flags); + if (args != null) { + data.writeInt(1); + args.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(SCHEDULE_SERVICE_ARGS_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 45c202d..c3e7224 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -149,7 +149,8 @@ public interface IActivityManager extends IInterface { public void unbindFinished(IBinder token, Intent service, boolean doRebind) throws RemoteException; /* oneway */ - public void serviceDoneExecuting(IBinder token) throws RemoteException; + public void serviceDoneExecuting(IBinder token, int type, int startId, + int res) throws RemoteException; public IBinder peekService(Intent service, String resolvedType) throws RemoteException; public boolean bindBackupAgent(ApplicationInfo appInfo, int backupRestoreMode) diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index c915770..6faaa34 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -71,7 +71,8 @@ public interface IApplicationThread extends IInterface { Intent intent, boolean rebind) throws RemoteException; void scheduleUnbindService(IBinder token, Intent intent) throws RemoteException; - void scheduleServiceArgs(IBinder token, int startId, Intent args) throws RemoteException; + void scheduleServiceArgs(IBinder token, int startId, int flags, Intent args) + throws RemoteException; void scheduleStopService(IBinder token) throws RemoteException; static final int DEBUG_OFF = 0; static final int DEBUG_ON = 1; diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java index 2b12a2a..804c8eb 100644 --- a/core/java/android/app/IntentService.java +++ b/core/java/android/app/IntentService.java @@ -18,6 +18,7 @@ public abstract class IntentService extends Service { private volatile Looper mServiceLooper; private volatile ServiceHandler mServiceHandler; private String mName; + private boolean mRedelivery; private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { @@ -36,6 +37,19 @@ public abstract class IntentService extends Service { mName = name; } + /** + * Control redelivery of intents. If called with true, + * {@link #onStartCommand(Intent, int, int)} will return + * {@link Service#START_REDELIVER_INTENT} instead of + * {@link Service#START_NOT_STICKY}, so that if this service's process + * is called while it is executing the Intent in + * {@link #onHandleIntent(Intent)}, then when later restarted the same Intent + * will be re-delivered to it, to retry its execution. + */ + public void setIntentRedelivery(boolean enabled) { + mRedelivery = enabled; + } + @Override public void onCreate() { super.onCreate(); @@ -48,7 +62,6 @@ public abstract class IntentService extends Service { @Override public void onStart(Intent intent, int startId) { - super.onStart(intent, startId); Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; @@ -56,6 +69,12 @@ public abstract class IntentService extends Service { } @Override + public int onStartCommand(Intent intent, int flags, int startId) { + onStart(intent, startId); + return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; + } + + @Override public void onDestroy() { mServiceLooper.quit(); } diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 2dedaa7..60c756b 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -22,6 +22,7 @@ import android.content.Intent; import android.content.ContextWrapper; import android.content.Context; import android.content.res.Configuration; +import android.os.Build; import android.os.RemoteException; import android.os.IBinder; import android.util.Log; @@ -169,20 +170,119 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac } /** + * @deprecated Implement {@link #onStartCommand(Intent, int, int)} instead. + */ + @Deprecated + public void onStart(Intent intent, int startId) { + } + + /** + * Bits returned by {@link #onStartCommand} describing how to continue + * the service if it is killed. May be {@link #START_STICKY}, + * {@link #START_NOT_STICKY}, {@link #START_REDELIVER_INTENT}, + * or {@link #START_STICKY_COMPATIBILITY}. + */ + public static final int START_CONTINUATION_MASK = 0xf; + + /** + * Constant to return from {@link #onStartCommand}: compatibility + * version of {@link #START_STICKY} that does not guarantee that + * {@link #onStartCommand} will be called again after being killed. + */ + public static final int START_STICKY_COMPATIBILITY = 0; + + /** + * Constant to return from {@link #onStartCommand}: if this service's + * process is killed while it is started (after returning from + * {@link #onStartCommand}), then leave it in the started state but + * don't retain this delivered intent. Later the system will try to + * re-create the service, but it will <em>not</em> call + * {@link #onStartCommand} unless there has been a new call to + * {@link Context#startService Context.startService(Intent)} with a new + * Intent to deliver. + * + * <p>This mode makes sense for things that will be explicitly started + * and stopped to run for arbitrary periods of time, such as a service + * performing background music playback. + */ + public static final int START_STICKY = 1; + + /** + * Constant to return from {@link #onStartCommand}: if this service's + * process is killed while it is started (after returning from + * {@link #onStartCommand}), and there are no new start intents to + * deliver to it, then take the service out of the started state and + * don't recreate until a future explicit call to + * {@link Context#startService Context.startService(Intent)}. + * + * <p>This mode makes sense for things that want to do some work as a + * result of being started, but can be stopped when under memory pressure + * and will explicit start themselves again later to do more work. An + * example of such a service would be one that polls for data from + * a server: it could schedule an alarm to poll every N minutes by having + * the alarm start its service. When its {@link #onStartCommand} is + * called from the alarm, it schedules a new alarm for N minutes later, + * and spawns a thread to do its networking. If its process is killed + * while doing that check, the service will not be restarted until the + * alarm goes off. + */ + public static final int START_NOT_STICKY = 2; + + /** + * Constant to return from {@link #onStartCommand}: if this service's + * process is killed while it is started (after returning from + * {@link #onStartCommand}), then it will be scheduled for a restart + * and the last delivered Intent re-delivered to it again via + * {@link #onStartCommand}. This Intent will remain scheduled for + * redelivery until the service calls {@link #stopSelf(int)} with the + * start ID provided to {@link #onStartCommand}. + */ + public static final int START_REDELIVER_INTENT = 3; + + /** + * This flag is set in {@link #onStartCommand} if the Intent is a + * re-delivery of a previously delivered intent, because the service + * had previously returned {@link #START_REDELIVER_INTENT} but had been + * killed before calling {@link #stopSelf(int)} for that Intent. + */ + public static final int START_FLAG_REDELIVERY = 0x0001; + + /** + * This flag is set in {@link #onStartCommand} if the Intent is a + * a retry because the original attempt never got to or returned from + * {@link #onStartCommand(Intent, int, int)}. + */ + public static final int START_FLAG_RETRY = 0x0002; + + /** * Called by the system every time a client explicitly starts the service by calling * {@link android.content.Context#startService}, providing the arguments it supplied and a * unique integer token representing the start request. Do not call this method directly. - * + * + * <p>For backwards compatibility, the default implementation calls + * {@link #onStart} and returns either {@link #START_STICKY} + * or {@link #START_STICKY_COMPATIBILITY}. + * * @param intent The Intent supplied to {@link android.content.Context#startService}, - * as given. + * as given. This may be null if the service is being restarted after + * its process has gone away, and it had previously returned anything + * except {@link #START_STICKY_COMPATIBILITY}. + * @param flags Additional data about this start request. Currently either + * 0, {@link #START_FLAG_REDELIVERY}, or {@link #START_FLAG_RETRY}. * @param startId A unique integer representing this specific request to - * start. Use with {@link #stopSelfResult(int)}. + * start. Use with {@link #stopSelfResult(int)}. + * + * @return The return value indicates what semantics the system should + * use for the service's current started state. It may be one of the + * constants associated with the {@link #START_CONTINUATION_MASK} bits. * * @see #stopSelfResult(int) */ - public void onStart(Intent intent, int startId) { + public int onStartCommand(Intent intent, int flags, int startId) { + onStart(intent, startId); + return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY; } - + /** * Called by the system to notify a Service that it is no longer used and is being removed. The * service should clean up an resources it holds (threads, registered @@ -393,6 +493,8 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac mToken = token; mApplication = application; mActivityManager = (IActivityManager)activityManager; + mStartCompatibility = getApplicationInfo().targetSdkVersion + < Build.VERSION_CODES.ECLAIR; } final String getClassName() { @@ -405,4 +507,5 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac private IBinder mToken = null; private Application mApplication = null; private IActivityManager mActivityManager = null; + private boolean mStartCompatibility = false; } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 1775a4b..ffd2686 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -132,6 +132,19 @@ public class Build { * </ul> */ public static final int DONUT = 4; + /** + * Current work on "Eclair" development branch. + * + * <p>Applications targeting this or a later release will get these + * new changes in behavior:</p> + * <ul> + * <li> The {@link android.app.Service#onStartCommand + * Service.onStartCommand} function will return the new + * {@link android.app.Service#START_STICKY} behavior instead of the + * old compatibility {@link android.app.Service#START_STICKY_COMPATIBILITY}. + * </ul> + */ + public static final int ECLAIR = CUR_DEVELOPMENT; } /** The type of build, like "user" or "eng". */ diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index ee9fa36..a4b0685 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -42,6 +42,7 @@ import android.app.Instrumentation; import android.app.Notification; import android.app.PendingIntent; import android.app.ResultInfo; +import android.app.Service; import android.backup.IBackupManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -9385,7 +9386,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen sr.app = null; sr.executeNesting = 0; mStoppingServices.remove(sr); - if (sr.bindings.size() > 0) { + + boolean hasClients = sr.bindings.size() > 0; + if (hasClients) { Iterator<IntentBindRecord> bindings = sr.bindings.values().iterator(); while (bindings.hasNext()) { @@ -9406,7 +9409,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } else if (!allowRestart) { bringDownServiceLocked(sr, true); } else { - scheduleServiceRestartLocked(sr); + boolean canceled = scheduleServiceRestartLocked(sr, true); + + // Should the service remain running? Note that in the + // extreme case of so many attempts to deliver a command + // that it failed, that we also will stop it here. + if (sr.startRequested && (sr.stopIfKilled || canceled)) { + if (sr.pendingStarts.size() == 0) { + sr.startRequested = false; + if (!hasClients) { + // Whoops, no reason to restart! + bringDownServiceLocked(sr, true); + } + } + } } } @@ -9845,35 +9861,55 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private final void sendServiceArgsLocked(ServiceRecord r, boolean oomAdjusted) { - final int N = r.startArgs.size(); + final int N = r.pendingStarts.size(); if (N == 0) { return; } - final int BASEID = r.lastStartId - N + 1; int i = 0; while (i < N) { try { - Intent args = r.startArgs.get(i); + ServiceRecord.StartItem si = r.pendingStarts.get(i); if (DEBUG_SERVICE) Log.v(TAG, "Sending arguments to service: " - + r.name + " " + r.intent + " args=" + args); + + r.name + " " + r.intent + " args=" + si.intent); + if (si.intent == null && N > 0) { + // If somehow we got a dummy start at the front, then + // just drop it here. + i++; + continue; + } bumpServiceExecutingLocked(r); if (!oomAdjusted) { oomAdjusted = true; updateOomAdjLocked(r.app); } - r.app.thread.scheduleServiceArgs(r, BASEID+i, args); + int flags = 0; + if (si.deliveryCount > 0) { + flags |= Service.START_FLAG_RETRY; + } + if (si.doneExecutingCount > 0) { + flags |= Service.START_FLAG_REDELIVERY; + } + r.app.thread.scheduleServiceArgs(r, si.id, flags, si.intent); + si.deliveredTime = SystemClock.uptimeMillis(); + r.deliveredStarts.add(si); + si.deliveryCount++; i++; + } catch (RemoteException e) { + // Remote process gone... we'll let the normal cleanup take + // care of this. + break; } catch (Exception e) { + Log.w(TAG, "Unexpected exception", e); break; } } if (i == N) { - r.startArgs.clear(); + r.pendingStarts.clear(); } else { while (i > 0) { - r.startArgs.remove(0); i--; + r.pendingStarts.remove(i); } } } @@ -9942,19 +9978,61 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } finally { if (!created) { app.services.remove(r); - scheduleServiceRestartLocked(r); + scheduleServiceRestartLocked(r, false); } } requestServiceBindingsLocked(r); + + // If the service is in the started state, and there are no + // pending arguments, then fake up one so its onStartCommand() will + // be called. + if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) { + r.lastStartId++; + if (r.lastStartId < 1) { + r.lastStartId = 1; + } + r.pendingStarts.add(new ServiceRecord.StartItem(r.lastStartId, null)); + } + sendServiceArgsLocked(r, true); } - private final void scheduleServiceRestartLocked(ServiceRecord r) { + private final boolean scheduleServiceRestartLocked(ServiceRecord r, + boolean allowCancel) { + boolean canceled = false; + + long minDuration = SERVICE_RESTART_DURATION; + long resetTime = minDuration*2*2*2; + + // Any delivered but not yet finished starts should be put back + // on the pending list. + final int N = r.deliveredStarts.size(); + if (N > 0) { + for (int i=N-1; i>=0; i--) { + ServiceRecord.StartItem si = r.deliveredStarts.get(i); + if (si.intent == null) { + // We'll generate this again if needed. + } else if (!allowCancel || (si.deliveryCount < ServiceRecord.MAX_DELIVERY_COUNT + && si.doneExecutingCount < ServiceRecord.MAX_DONE_EXECUTING_COUNT)) { + r.pendingStarts.add(0, si); + long dur = SystemClock.uptimeMillis() - si.deliveredTime; + dur *= 2; + if (minDuration < dur) minDuration = dur; + if (resetTime < dur) resetTime = dur; + } else { + Log.w(TAG, "Canceling start item " + si.intent + " in service " + + r.name); + canceled = true; + } + } + r.deliveredStarts.clear(); + } + r.totalRestartCount++; if (r.restartDelay == 0) { r.restartCount++; - r.restartDelay = SERVICE_RESTART_DURATION; + r.restartDelay = minDuration; } else { // If it has been a "reasonably long time" since the service // was started, then reset our restart duration back to @@ -9962,17 +10040,21 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // on a service that just occasionally gets killed (which is // a normal case, due to process being killed to reclaim memory). long now = SystemClock.uptimeMillis(); - if (now > (r.restartTime+(SERVICE_RESTART_DURATION*2*2*2))) { + if (now > (r.restartTime+resetTime)) { r.restartCount = 1; - r.restartDelay = SERVICE_RESTART_DURATION; + r.restartDelay = minDuration; } else { - r.restartDelay *= 2; + r.restartDelay *= 4; + if (r.restartDelay < minDuration) { + r.restartDelay = minDuration; + } } } if (!mRestartingServices.contains(r)) { mRestartingServices.add(r); } r.cancelNotification(); + mHandler.removeCallbacks(r.restarter); mHandler.postDelayed(r.restarter, r.restartDelay); r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay; @@ -9985,6 +10067,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen msg.what = SERVICE_ERROR_MSG; msg.obj = r; mHandler.sendMessage(msg); + + return canceled; } final void performServiceRestartLocked(ServiceRecord r) { @@ -10146,6 +10230,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.foregroundId = 0; r.foregroundNoti = null; + // Clear start entries. + r.deliveredStarts.clear(); + r.pendingStarts.clear(); + if (r.app != null) { synchronized (r.stats.getBatteryStats()) { r.stats.stopLaunchedLocked(); @@ -10207,11 +10295,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + r.shortName); } r.startRequested = true; - r.startArgs.add(service); + r.callStart = false; r.lastStartId++; if (r.lastStartId < 1) { r.lastStartId = 1; } + r.pendingStarts.add(new ServiceRecord.StartItem(r.lastStartId, service)); r.lastActivity = SystemClock.uptimeMillis(); synchronized (r.stats.getBatteryStats()) { r.stats.startRunningLocked(); @@ -10279,6 +10368,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.record.stats.stopRunningLocked(); } r.record.startRequested = false; + r.record.callStart = false; final long origId = Binder.clearCallingIdentity(); bringDownServiceLocked(r.record, false); Binder.restoreCallingIdentity(origId); @@ -10327,10 +10417,35 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_SERVICE) Log.v(TAG, "stopServiceToken: " + className + " " + token + " startId=" + startId); ServiceRecord r = findServiceLocked(className, token); - if (r != null && (startId < 0 || r.lastStartId == startId)) { + if (r != null) { + if (startId >= 0) { + // Asked to only stop if done with all work. Note that + // to avoid leaks, we will take this as dropping all + // start items up to and including this one. + ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); + if (si != null) { + while (r.deliveredStarts.size() > 0) { + if (r.deliveredStarts.remove(0) == si) { + break; + } + } + } + + if (r.lastStartId != startId) { + return false; + } + + if (r.deliveredStarts.size() > 0) { + Log.w(TAG, "stopServiceToken startId " + startId + + " is last, but have " + r.deliveredStarts.size() + + " remaining args"); + } + } + synchronized (r.stats.getBatteryStats()) { r.stats.stopRunningLocked(); r.startRequested = false; + r.callStart = false; } final long origId = Binder.clearCallingIdentity(); bringDownServiceLocked(r, false); @@ -10674,7 +10789,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - public void serviceDoneExecuting(IBinder token) { + public void serviceDoneExecuting(IBinder token, int type, int startId, int res) { synchronized(this) { if (!(token instanceof ServiceRecord)) { throw new IllegalArgumentException("Invalid service token"); @@ -10692,6 +10807,51 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return; } + if (type == 1) { + // This is a call from a service start... take care of + // book-keeping. + r.callStart = true; + switch (res) { + case Service.START_STICKY_COMPATIBILITY: + case Service.START_STICKY: { + // We are done with the associated start arguments. + r.findDeliveredStart(startId, true); + // Don't stop if killed. + r.stopIfKilled = false; + break; + } + case Service.START_NOT_STICKY: { + // We are done with the associated start arguments. + r.findDeliveredStart(startId, true); + if (r.lastStartId == startId) { + // There is no more work, and this service + // doesn't want to hang around if killed. + r.stopIfKilled = true; + } + break; + } + case Service.START_REDELIVER_INTENT: { + // We'll keep this item until they explicitly + // call stop for it, but keep track of the fact + // that it was delivered. + ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); + if (si != null) { + si.deliveryCount = 0; + si.doneExecutingCount++; + // Don't stop if killed. + r.stopIfKilled = true; + } + break; + } + default: + throw new IllegalArgumentException( + "Unknown service start result: " + res); + } + if (res == Service.START_STICKY_COMPATIBILITY) { + r.callStart = false; + } + } + final long origId = Binder.clearCallingIdentity(); serviceDoneExecutingLocked(r, inStopping); Binder.restoreCallingIdentity(origId); diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 9318a72..afbf9c7 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -64,7 +64,28 @@ class ServiceRecord extends Binder { final HashMap<IBinder, ConnectionRecord> connections = new HashMap<IBinder, ConnectionRecord>(); // IBinder -> ConnectionRecord of all bound clients - final List<Intent> startArgs = new ArrayList<Intent>(); + + // Maximum number of delivery attempts before giving up. + static final int MAX_DELIVERY_COUNT = 3; + + // Maximum number of times it can fail during execution before giving up. + static final int MAX_DONE_EXECUTING_COUNT = 6; + + static class StartItem { + final int id; + final Intent intent; + long deliveredTime; + int deliveryCount; + int doneExecutingCount; + + StartItem(int _id, Intent _intent) { + id = _id; + intent = _intent; + } + } + final ArrayList<StartItem> deliveredStarts = new ArrayList<StartItem>(); + // start() arguments which been delivered. + final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>(); // start() arguments that haven't yet been delivered. ProcessRecord app; // where this service is running or null. @@ -73,6 +94,8 @@ class ServiceRecord extends Binder { Notification foregroundNoti; // Notification record of foreground state. long lastActivity; // last time there was some activity on the service. boolean startRequested; // someone explicitly called start? + boolean stopIfKilled; // last onStart() said to stop if service killed? + boolean callStart; // last onStart() has asked to alway be called on restart. int lastStartId; // identifier of most recent start request. int executeNesting; // number of outstanding operations keeping foreground. long executingStart; // start time of last execute request. @@ -85,6 +108,25 @@ class ServiceRecord extends Binder { String stringName; // caching of toString + void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) { + final int N = list.size(); + for (int i=0; i<N; i++) { + StartItem si = list.get(i); + pw.print(prefix); pw.print("#"); pw.print(i); + pw.print(" id="); pw.print(si.id); + if (now != 0) pw.print(" dur="); pw.print(now-si.deliveredTime); + if (si.deliveryCount != 0) { + pw.print(" dc="); pw.print(si.deliveryCount); + } + if (si.doneExecutingCount != 0) { + pw.print(" dxc="); pw.print(si.doneExecutingCount); + } + pw.print(" "); + if (si.intent != null) pw.println(si.intent.toString()); + else pw.println("null"); + } + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("intent={"); pw.print(intent.getIntent().toShortString(true, false)); @@ -108,6 +150,8 @@ class ServiceRecord extends Binder { pw.print(" restartTime="); pw.println(restartTime); if (startRequested || lastStartId != 0) { pw.print(prefix); pw.print("startRequested="); pw.print(startRequested); + pw.print(" stopIfKilled="); pw.print(stopIfKilled); + pw.print(" callStart="); pw.print(callStart); pw.print(" lastStartId="); pw.println(lastStartId); } if (executeNesting != 0 || crashCount != 0 || restartCount != 0 @@ -118,8 +162,17 @@ class ServiceRecord extends Binder { pw.print(" nextRestartTime="); pw.print(nextRestartTime); pw.print(" crashCount="); pw.println(crashCount); } + if (deliveredStarts.size() > 0) { + pw.print(prefix); pw.println("Delivered Starts:"); + dumpStartList(pw, prefix, deliveredStarts, SystemClock.uptimeMillis()); + } + if (pendingStarts.size() > 0) { + pw.print(prefix); pw.println("Pending Starts:"); + dumpStartList(pw, prefix, pendingStarts, 0); + } if (bindings.size() > 0) { Iterator<IntentBindRecord> it = bindings.values().iterator(); + pw.print(prefix); pw.println("Bindings:"); while (it.hasNext()) { IntentBindRecord b = it.next(); pw.print(prefix); pw.print("* IntentBindRecord{"); @@ -180,6 +233,19 @@ class ServiceRecord extends Binder { restartTime = 0; } + public StartItem findDeliveredStart(int id, boolean remove) { + final int N = deliveredStarts.size(); + for (int i=0; i<N; i++) { + StartItem si = deliveredStarts.get(i); + if (si.id == id) { + if (remove) deliveredStarts.remove(i); + return si; + } + } + + return null; + } + public void postNotification() { if (foregroundId != 0 && foregroundNoti != null) { INotificationManager inm = NotificationManager.getService(); |
