0

いくつかの予備情報:

Android ライブラリの API を作成する必要があります。この API は、汎用データの基本的な CRUD (検索、すべて検索、挿入、更新、削除) 操作を処理する必要があります。その後、このライブラリを使用する他のアプリは、必要なデータ/オブジェクトでこの API を使用できます。ただし、このデータはバックエンド サーバーに保存され、異なるデバイス (基本的には BaaS) で同じアプリを使用するすべてのユーザーによって保存され、使用されます。アプリは、インターネットに接続しなくても動作できるはずです。そこで、Android で REST クライアントを設計する方法について Google I/O が作成したビデオをチェックして、それに従いました。

したがって、今:

  • データを挿入/更新/削除する場合、API はローカル コンテンツ プロバイダーを使用し、特定のフラグをデータに設定します。
  • 私は SyncAdapter を実行しています。これは、実行ごとに sqlite のすべてのデータをチェックします。
  • データのフラグをチェックし (挿入、更新、または削除された場合)、REST API を呼び出してサーバーと同期します。

ただし、アプリがこのシンクロナイゼーションにコールバックを渡せるようにしたいと考えています。基本的に、私が望むのは、アプリが API から「挿入」メソッドを呼び出すときに、コールバックを渡す必要があるということです。このコールバックは 2 つのことを行う必要があります。

  • 挿入が失敗した場合の対処方法を定義する
  • 挿入が成功した場合の処理​​を定義します。

これは私がやりたいことの例です:

GenericData data = new GenericData();
//Initialize data
API.insert(GenericData.class, data, new CallBack<GenericData>(){

    @Override
    public void onSuccess(GenericData insertedData){
        //Data inserted and syncronized successfully
    }

    @Override
    public void onFailure(){
        //Data failed to be inserted an/or sincronized
    }
});

このコールバックは匿名クラスとして、アプリの AsyncTask から呼び出されます。私がやりたいことはcallBack.onSuccess(data)、同期が成功した場合は SyncAdapter からの呼び出し、またはcallBack.onFailure()同期が失敗した場合は SyncAdapter からの呼び出しです。

したがって、Callback クラスは次のようになります。

//Concrete empty methods are set so it's not necessary to implement all these methods
public abstract class Callback<T>{
    public void onSuccess(T insertedData){}
    public void onFailure(){}
}

私が念頭に置いていたのはこれでした:

「挿入」コールバックを処理する BroadcastReceiver を作成します。このレシーバーは、特定のインテントをリッスンします。

SyncAdapter は、挿入されたデータの行の同期を完了すると、特定のインテントを作成してブロードキャストします。このインテントには、追加データとして、処理された行の ID と、同期のステータス (成功したか失敗したか) が含まれます。レシーバーが呼び出されると、インテントから行の ID を取得し、その ID の特定のコールバックを取得し、同期呼び出しのステータスに応じてcallBack.onSuccess(data)またはcallBack.onFailure().

私の問題は、呼び出すコールバックを決定することにあります。各「API.insert(..)」メソッドCallbackには、無名クラスであるサブクラスが渡されます。それをシリアル化する方法、または行の特定のIDで「保存」する方法がわかりません。何らかの方法でシリアル化できる場合、BroadcastReceiver はCallback callback = lookup(id)その ID に関連付けられたコールバックを取得します。ただし、コールバックをシリアル化しようとしたが機能しなかったため、これが可能かどうかはわかりません。問題は、Activity 自体 (insert メソッドが呼び出される場所) 自体がシリアル化できないため、コールバックもシリアル化できないことだと思います。

匿名クラスを使用しないことを考えましたが、代わりに名前付きの Callback クラスを使用しました。ただし、メソッドでオブジェクトを渡すことを許可した場合Callbackでも、匿名クラスを渡すことができます。したがって、まさにそれを行うアプリにはエラーが発生します。したがって、コールバックは、匿名クラスとして渡された場合でも機能するはずです。上記の状況に対する代替手段がない限り。

また、アクティビティ内で無名クラスにして、アクティビティで定義した変数をコールバックで使えるようにしてほしいです。たとえば、2 つの異なるオブジェクトを連続して挿入するには (コールバックはOtherData dataアクティビティで定義されたオブジェクトを使用します)。何らかの方法で実行でき、Activity がまだ稼働している場合、コールバックは同じ Activity オブジェクトを使用する必要があります。ただし、アクティビティが閉じている場合は、どういうわけかシリアル化して、コールバックが後で呼び出されると、クローア/破棄される直前のアクティビティの変数/データを使用します

何か案は?

PS:また、私が言ったように、コールバックとデータはジェネリックである必要があるため、コールバックを呼び出すメソッドは可能なすべての型をサポートする必要があります。とにかく、ワイルドカードを使用するなど、これは問題にならないと思いますCallback<?> callback = lookupCallback(id)


アップデート:

わかりました、おそらく解決策があると思います。

次に、私が抱えていた主な問題は、コールバックを匿名クラスにすると同時にシリアライズ可能にする必要があることでした。それは不可能です。シリアライズ可能であるが無名クラスではない場合、それを呼び出すアクティビティから変数/属性を使用できません (これらのコールバックを処理するために必要です)。しかし、それが匿名クラスであるがシリアライズ可能でない場合、コールバックをシリアライズしてデシリアライズし、SyncAdapter が同期を終了したときに呼び出されるようにすることはできません。

だから私は、両方の長所を含めるために、このようにすることができるのではないかと考えました:

これは CallBack クラスになります。

//Concrete empty methods are set so it's not necessary to implement all these methods
public abstract class Callback<T> implements Serializable{
    public void onSuccess(T insertedData){}
    public void onFailure(){}
}

すべてのコールバックは名前付きクラス (外部クラス、または静的内部クラスなど) である必要があり、その作成者でアクティビティを渡します。次に、必要なアクティビティから任意のフィールドを取得できます。

これは例を示したほうがよいと思うので、「ユーザーとそのユーザーを持つアドレスを作成します。ユーザーを挿入し、ユーザーが挿入されたことがわかったらアドレスを挿入したい」 .

この場合、次のようなコールバックがあると思います。

public class UserInsertedCallback extends Callback<User> implements Serializable{
    //Here goes serialId
    private Address address;

    public UserInsertedCallback(UserActivity activity){
        address = activity.getAddress();
    }

    @Override
    public void onSuccess(User insertedUser){
        //This is another callback we may want to use
        Callback<Address> callback = createCallback();
        //I create Foreign Key in Address referencing the user
        address.setUserId(insertedUser.getId());
        API.insert(Address.class, address, callback); 
    }
}

これがアクティビティになります。

public class UserActivity extends Activity{
    private Address address;
    ....
    public Address getAddress(){return address;}

    private class TestTask extends AsyncTask<Void,Void,Void>{
       @Override
       protected Void doInBackground(Void... void){
            User user = ... //create user
            address = ... //create address
            API.insert(User.class, user, new UserInsertedCallback(UserActivity.this));
        }
    }
}

私の API メソッドは UserInsertedCallback をシリアル化し、簡単に取得できるデータベース テーブルに挿入します。Address がシリアライズ可能であると仮定すると、ここで問題はありません。そうでない場合でも、開発者はシリアル化可能なオブジェクト/プリミティブを UserInsertedCallback に含めるだけで、 Address オブジェクトを で再度作成できますonSuccess()。コールバックがシリアル化されると、SyncAdapter が User の挿入を正常に完了するたびに、シリアル化されたコールバックをデータベースから取得し、逆シリアル化し、そこから "onSuccess(insertedUser)" を呼び出すことができます。

Address オブジェクトだけが必要な場合は、代わりに UserInsertedCallback のコンストラクターがそれを受け取ることができます。しかし、おそらく開発者はアクティビティから他のものも必要とするので、アクティビティを渡すこともできます。

確かに、それはそれほど簡単ではないと確信していますが、すぐにこれが機能するかどうかを確認してみます. 人々がコールバックを匿名クラスとして渡すのを防ぐ方法はまだわかりません

4

2 に答える 2

1

So I was finally able to get this going on.
It worked like I put it in my Edit, but I'll give some more explanations:

Basically, the Callback class is similar to how I defined it above, except I changed the methods a little bit:

public abstract class Callback<T> implements Serializable{
    private static final long serialVersionID = ...; //version Id goes here
    public void onSuccess(T insertedData){}
    public void onFailure(T failedData, CustomException ex){}
}

I pass more info to the onFailure event, such as the data that failed the synchronization, and a custom exception thrown by the synchronization (since the synchronization can fail for a lot of reasons).

Then each app can create a named class that extends Callback<T>, and can have any field it wants (as long as it's serializable), just like I mentioned in the Edit. My API call takes this callback as a parameter, serializes it, and stores it in the database alongside with the _ID of the data to synchronize.
Later on, my SyncAdapter takes that data and tries to synchronize it with the server. If a fatal error occurs (one it can't recover from and try again later for instance), it reverts the data back to its original state and sends a broadcast message passing the serialized data, a serialized exception (both via JSON), the _ID field, and passing a parameter stating the synchronization failed.
Then I set up a BroadcastReceiver who listens for this broadcast, gets all this information and starts a Service with the same extras.
This new service gets the _ID field, looks up the serialized callback from the database, deserializes it, deserializes the data and the custom exception, and calls callback.onFailure(data, ex), and it works pretty well!
Once the callback is finished being called, it is deleted from the database. All of this (except defining the extended callback class and calling the API) is done in the Android library itself.

P.S: Actually, the data is in JSON format and de/serialized with GSON. To do this I added a Class<T> data_class; field to Callback<T>, and a onFailureFromJson(String json, CustomException ex) final method which deserializes the JSON into an object of type T, and calls onFailure(entity,ex) with it. Thus the service calls this onFailureFromJson method instead.
Here's the final Callback<T> class:

public abstract class Callback<T> implements Serializable{
    private static final long serialVersionID = ...; //version Id goes here
    private Class<T> data_class;

    public Callback(Class<T> data_class){this.data_class = data_class;}
    public abstract void onSuccess(T insertedData){}
    public abstract void onFailure(T failedData, CustomException ex){}

    public final void onSuccessFromJson(String json){
        Gson gson = new GsonBuilder().create();
        T entity = gson.fromJson(json,data_class);
        onSuccess(entity);
    }
    public final void onFailureFromJson(String json, CustonException ex){
        Gson gson = new GsonBuilder().create();
        T entity = gson.fromJson(json,data_class);
        onFailure(entity,ex);    
    }    
}
于 2013-11-16T04:44:27.487 に答える