onServiceConnected sometimes not called after bind

2019-02-24 04:22发布

问题:

I've looked at a number of other threads with similar titles, and none seem to cover my problem. So, here goes.

I'm using the Google market expansion files (apkx) library and sample code, with a few modifications. This code relies on receiving callbacks from a service which handles background downloading, licence checks etc.

I have a bug where the service doesn't get correctly attached, which results in a softlock. To make this more unhelpful, this bug never happens on some devices, but occurs about two thirds of the time on other devices. I believe it to be independent of Android version, certainly I have two devices running 2.3.4, one of which (a Nexus S) doesn't have the problem, the other (an HTC Evo 3D) does.

To attempt to connect to the service, bindService is called and returns true. OnBind then gets called as expected and returns a sensible value but (when the bug occurs) onServiceConnected doesn't happen (I've waited 20 minutes just in case).

Has anyone else seen anything like this? If not, any guesses for what I might have done to cause such behaviour? If no-one has any thoughts, I'll post some code tomorrow.

EDIT: Here's the relevant code. If I've missed anything, please ask.

Whilst adding this code, I found a minor bug. Fixing it caused the frequency of the problem I'm trying to solve to change from 2 times in 3 to about 1 time in 6 on the phone I'm testing it on; no idea about effects on other phones. This continues to suggest to me a race condition or similar, but I've no idea what with.

OurDownloaderActivity.java (copied and changed from Google sample code)

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ...

    //Test the licence is up to date
    //if (current stored licence has expired)
    {
        startLicenceCheck();
        initializeDownloadUI();
        return;
    }

    ...
}

@Override
protected void onResume() {
    if (null != mDownloaderClientStub) {
        mDownloaderClientStub.connect(this);
    }
    super.onResume();
}

private void startLicenceCheck()
{
    Intent launchIntent = OurDownloaderActivity.this
            .getIntent();
    Intent intentToLaunchThisActivityFromNotification = new Intent(OurDownloaderActivity
            .this, OurDownloaderActivity.this.getClass());
    intentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
            Intent.FLAG_ACTIVITY_CLEAR_TOP);
    intentToLaunchThisActivityFromNotification.setAction(launchIntent.getAction());

    if (launchIntent.getCategories() != null) {
        for (String category : launchIntent.getCategories()) {
            intentToLaunchThisActivityFromNotification.addCategory(category);
        }
    }

    // Build PendingIntent used to open this activity from Notification
    PendingIntent pendingIntent = PendingIntent.getActivity(OurDownloaderActivity.this,
            0, intentToLaunchThisActivityFromNotification,
            PendingIntent.FLAG_UPDATE_CURRENT);

    DownloaderService.startLicenceCheck(this, pendingIntent, OurDownloaderService.class);
}

initializeDownloadUI()
{
    mDownloaderClientStub = DownloaderClientMarshaller.CreateStub
            (this, OurDownloaderService.class);

    //do a load of UI setup
    ...
}

//This should be called by the Stub's onServiceConnected method
/**
 * Critical implementation detail. In onServiceConnected we create the
 * remote service and marshaler. This is how we pass the client information
 * back to the service so the client can be properly notified of changes. We
 * must do this every time we reconnect to the service.
 */
@Override
public void onServiceConnected(Messenger m) {
    mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
    mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}

DownloaderService.java (in Google market expansion library but somewhat edited )

//this is the onBind call that happens fine; the value it returns is definitely not null
@Override
public IBinder onBind(Intent paramIntent) {
    return this.mServiceMessenger.getBinder();
}

final private IStub mServiceStub = DownloaderServiceMarshaller.CreateStub(this);
final private Messenger mServiceMessenger = mServiceStub.getMessenger();

//MY CODE, derived from Google's code
//I have seen the bug occur with a service started by Google's code too,
//but this code happens more often so is more repeatably related to the problem
public static void startLicenceCheck(Context context, PendingIntent pendingIntent, Class<?> serviceClass)
{       
    String packageName = serviceClass.getPackage().getName();
    String className = serviceClass.getName();

    Intent fileIntent = new Intent();
    fileIntent.setClassName(packageName, className);
    fileIntent.putExtra(EXTRA_LICENCE_EXPIRED, true);
    fileIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
    context.startService(fileIntent);
}

@Override
protected void onHandleIntent(Intent intent) {
    setServiceRunning(true);
    try {
        final PendingIntent pendingIntent = (PendingIntent) intent
            .getParcelableExtra(EXTRA_PENDING_INTENT);

        if (null != pendingIntent)
        {
            mNotification.setClientIntent(pendingIntent);
            mPendingIntent = pendingIntent;
        } else if (null != mPendingIntent) {
            mNotification.setClientIntent(mPendingIntent);
        } else {
            Log.e(LOG_TAG, "Downloader started in bad state without notification intent.");
            return;
        }

        if(intent.getBooleanExtra(EXTRA_LICENCE_EXPIRED, false))
        {
            //we are here due to startLicenceCheck
            updateExpiredLVL(this);
            return;
        }
    ...
    }
}

//MY CODE, based on Google's, again
public void updateExpiredLVL(final Context context) {
    Context c = context.getApplicationContext();
    Handler h = new Handler(c.getMainLooper());
    h.post(new LVLExpiredUpdateRunnable(c));
}

private class LVLExpiredUpdateRunnable implements Runnable
{
    LVLExpiredUpdateRunnable(Context context) {
        mContext = context;
    }

    final Context mContext;

    @Override
    public void run() {
        setServiceRunning(true);
        mNotification.onDownloadStateChanged(IDownloaderClient.STATE_LVL_UPDATING);
        String deviceId = getDeviceId(mContext);

        final APKExpansionPolicy aep = new APKExpansionPolicy(mContext,
                new AESObfuscator(getSALT(), mContext.getPackageName(), deviceId));

        // Construct the LicenseChecker with a Policy.
        final LicenseChecker checker = new LicenseChecker(mContext, aep,
                getPublicKey() // Your public licensing key.
        );
        checker.checkAccess(new LicenseCheckerCallback() {
            ...
        });
    }
}

DownloaderClientMarshaller.java (in Google market expansion library)

public static IStub CreateStub(IDownloaderClient itf, Class<?> downloaderService) {
    return new Stub(itf, downloaderService);
}

and the Stub class from the same file:

private static class Stub implements IStub {
    private IDownloaderClient mItf = null;
    private Class<?> mDownloaderServiceClass;
    private boolean mBound;
    private Messenger mServiceMessenger;
    private Context mContext;
    /**
     * Target we publish for clients to send messages to IncomingHandler.
     */
    final Messenger mMessenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_ONDOWNLOADPROGRESS:                        
                    Bundle bun = msg.getData();
                    if ( null != mContext ) {
                        bun.setClassLoader(mContext.getClassLoader());
                        DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData()
                                .getParcelable(PARAM_PROGRESS);
                        mItf.onDownloadProgress(dpi);
                    }
                    break;
                case MSG_ONDOWNLOADSTATE_CHANGED:
                    mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE));
                    break;
                case MSG_ONSERVICECONNECTED:
                    mItf.onServiceConnected(
                            (Messenger) msg.getData().getParcelable(PARAM_MESSENGER));
                    break;
            }
        }
    });

    public Stub(IDownloaderClient itf, Class<?> downloaderService) {
        mItf = itf;
        mDownloaderServiceClass = downloaderService;
    }

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {

        //this is the critical call that never happens

        public void onServiceConnected(ComponentName className, IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the object we can use to
            // interact with the service. We are communicating with the
            // service using a Messenger, so here we get a client-side
            // representation of that from the raw IBinder object.
            mServiceMessenger = new Messenger(service);
            mItf.onServiceConnected(
                    mServiceMessenger);
            mBound = true;                
        }

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

    @Override
    public void connect(Context c) {
        mContext = c;
        Intent bindIntent = new Intent(c, mDownloaderServiceClass);
        bindIntent.putExtra(PARAM_MESSENGER, mMessenger);
        if ( !c.bindService(bindIntent, mConnection, 0) ) {
            if ( Constants.LOGVV ) {
                Log.d(Constants.TAG, "Service Unbound");
            }
        }

    }

    @Override
    public void disconnect(Context c) {
        if (mBound) {
            c.unbindService(mConnection);
            mBound = false;
        }
        mContext = null;
    }

    @Override
    public Messenger getMessenger() {
        return mMessenger;
    }
}

DownloaderServiceMarshaller.java (in Google market expansion library, unchanged)

private static class Proxy implements IDownloaderService {
    private Messenger mMsg;

    private void send(int method, Bundle params) {
        Message m = Message.obtain(null, method);
        m.setData(params);
        try {
            mMsg.send(m);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public Proxy(Messenger msg) {
        mMsg = msg;
    }

    @Override
    public void requestAbortDownload() {
        send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle());
    }

    @Override
    public void requestPauseDownload() {
        send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle());
    }

    @Override
    public void setDownloadFlags(int flags) {
        Bundle params = new Bundle();
        params.putInt(PARAMS_FLAGS, flags);
        send(MSG_SET_DOWNLOAD_FLAGS, params);
    }

    @Override
    public void requestContinueDownload() {
        send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle());
    }

    @Override
    public void requestDownloadStatus() {
        send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle());
    }

    @Override
    public void onClientUpdated(Messenger clientMessenger) {
        Bundle bundle = new Bundle(1);
        bundle.putParcelable(PARAM_MESSENGER, clientMessenger);
        send(MSG_REQUEST_CLIENT_UPDATE, bundle);
    }
}

private static class Stub implements IStub {
    private IDownloaderService mItf = null;
    final Messenger mMessenger = new Messenger(new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_REQUEST_ABORT_DOWNLOAD:
                    mItf.requestAbortDownload();
                    break;
                case MSG_REQUEST_CONTINUE_DOWNLOAD:
                    mItf.requestContinueDownload();
                    break;
                case MSG_REQUEST_PAUSE_DOWNLOAD:
                    mItf.requestPauseDownload();
                    break;
                case MSG_SET_DOWNLOAD_FLAGS:
                    mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS));
                    break;
                case MSG_REQUEST_DOWNLOAD_STATE:
                    mItf.requestDownloadStatus();
                    break;
                case MSG_REQUEST_CLIENT_UPDATE:
                    mItf.onClientUpdated((Messenger) msg.getData().getParcelable(
                            PARAM_MESSENGER));
                    break;
            }
        }
    });

    public Stub(IDownloaderService itf) {
        mItf = itf;
    }

    @Override
    public Messenger getMessenger() {
        return mMessenger;
    }

    @Override
    public void connect(Context c) {

    }

    @Override
    public void disconnect(Context c) {

    }
}

/**
 * Returns a proxy that will marshall calls to IDownloaderService methods
 * 
 * @param ctx
 * @return
 */
public static IDownloaderService CreateProxy(Messenger msg) {
    return new Proxy(msg);
}

/**
 * Returns a stub object that, when connected, will listen for marshalled
 * IDownloaderService methods and translate them into calls to the supplied
 * interface.
 * 
 * @param itf An implementation of IDownloaderService that will be called
 *            when remote method calls are unmarshalled.
 * @return
 */
public static IStub CreateStub(IDownloaderService itf) {
    return new Stub(itf);
}