OnServiceConnected czasami nie wywołuje po bindService na niektórych urządzeniach

Przeglądałem wiele innych wątków o podobnych tytułach i żaden z nich nie wydaje się obejmować mojego problemu. Tak więc idzie.

Używam biblioteki plików ekspansji Google (apkx) i przykładowego kodu, z kilkoma modyfikacjami. Ten kod polega na otrzymywaniu wywołań zwrotnych z usługi, która obsługuje pobieranie w tle, sprawdzanie licencji itp.

Mam błąd, w którym usługa nie jest poprawnie dołączona, co powoduje softlock. Aby uczynić to bardziej nieprzydatnym, ten błąd nigdy nie występuje na niektórych urządzeniach, ale występuje około dwóch trzecich czasu na innych urządzeniach. Uważam, że jest niezależny od wersji Androida, na pewno mam dwa urządzenia z 2.3.4, z których jeden (Nexus S) nie ma problemu, drugi (HTC Evo 3D).

Aby połączyć się z usługą, wywoływana jest funkcja bindService i zwraca true. OnBind jest następnie wywoływany zgodnie z oczekiwaniami i zwraca sensowną wartość, ale (gdy wystąpi błąd) onServiceConnected nie zdarza się (na wszelki wypadek czekałem 20 minut).

Czy ktoś jeszcze widział coś takiego? Jeśli nie, jakieś domysły na temat tego, co mogłem zrobić, aby spowodować takie zachowanie? Jeśli nikt nie ma myśli, jutro opublikuję kod.

EDIT: Oto odpowiedni kod. Jeśli coś przegapiłem, zapytaj.

Podczas dodawania tego kodu znalazłem drobny błąd. Naprawienie go spowodowało, że częstotliwość problemu, który próbuję rozwiązać, zmienia się z 2 razy w 3 do około 1 raz na 6 w telefonie, na którym testuję; nie mam pojęcia o efektach na innych telefonach. To nadal sugeruje mi stan wyścigu lub podobny, ale nie mam pojęcia, z czym.

OurDownloaderActivity.java (skopiowane i zmienione z przykładowego kodu Google)

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 (w bibliotece ekspansji Google, ale nieco zmodyfikowana)

//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 (w bibliotece ekspansji Google)

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

a klasa Stub z tego samego pliku:

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 (w bibliotece rozszerzeń na rynku Google, bez zmian)

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);
}

questionAnswers(0)

yourAnswerToTheQuestion