如何(RxJava)正确设置处理器将处理从Observable发出的数据的条件?

时间:2016-10-30 01:52:51

标签: android retrofit rx-java rx-android

因此,我的任务是将设备的位置信息(如果更改)推送到远程服务器Json API服务。如果远程服务器不可用,我的DatabaseManager必须将它们保存到本地数据库。

这是我的Retrofit API:

    public interface GpsService { 
        @POST("/v1/savelocationbatch") 
        SaveResponse saveLocationBatch(@Body LocationBatch locationBatch); 
    }

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl(myBaseUrl)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

    GpsService service = retrofit.create(GpsService.class);

和POJO课程:

    public class LocationBatch{

        @SerializedName("LocationPointList")
        ArrayList<LocationPoint> locationPointList;
        @SerializedName("ClientId")
        String clientId;
        @SerializedName("ClientSecret")
        String clientSecret;

        //setter & getter
    }

我的LocationPoint模型:

    @Table(name="LocationPoints", id = "_id")
    public class LocationPoint extends Model {

        @SerializedName("Latitude")
        @Column(name="latitude")
        public Double latitude;

        @SerializedName("Longitude")
        @Column(name="longitude")
        public Double longitude;

        @SerializedName("Altitude")
        @Column(name="altitude")
        public Double altitude;

        //... setters, getters etc
}

我的所有上一个位置都存储在 CurrentLocationHolder 单例中(用于批量发送/保存到DB /从Observable发出)。它的setLocation()方法更新currentLocation变量,然后将其放入locationBuffer,而不是检查缓冲区的大小,而不是缓冲区的大小超过我的MAX_BUFFER_SIZE变量,它会触发locationBufferChanged.onNext(使用locationBuffer的副本作为参数),然后清除它locationBuffer ...

    public class CurrentLocationHolder {

        private List<LocationPoint> locationBuffer = 
                            Collections.synchronizedList(new ArrayList<>());
        private LocationPoint currentLocation;
        private final PublishSubject<List<LocationPoint>> locationBufferFull = 
                            PublishSubject.create();

        public Observable<List<LocationPoint>> 
                            observeLocationBufferFull(boolean emitCurrentValue) {
                return emitCurrentValue ?
                    locationBufferFull.startWith(locationBuffer) :
                    locationBufferFull;
            }

        public void setLocation(LocationPoint point) {
            this.currentLocation = point;
            locationBuffer.add(point);
            if (locationBuffer.size() >= MAX_BUFFER_SIZE) {
                locationBufferChanged.onNext(new ArrayList<>(this.locationBuffer));
            }
            locationBuffer.clear();
        }
    }

这是我的DatabaseManager:

    public class DatabaseManager {    
        private Subscription locationBufferSubscription;
        private static DatabaseManager instance;    
        public static void InitInstance() {
            if (instance == null) 
                instance = new DatabaseManager();
            }
        }

        public void saveToDb(ArrayList<LocationPoint> locArray){
            ActiveAndroid.beginTransaction();
            try {
                for (int i = 0; i < locArray.size(); i++) {
                    locArray.get(i).save();
                }
                ActiveAndroid.setTransactionSuccessful();
            } 
            finally {
                ActiveAndroid.endTransaction();
            }
        }
    }

我的应用程序的主要目标:

通过Retrofit将所有已侦听的LocationPoints写入HTTP服务器。如果远程服务器因某种原因突然停机(或者互联网连接丢失),我的应用程序应该无缝地将新的locationPoints写入本地数据库。当服务器(或互联网)启动时,某些机制应该将已保存的本地数据提供给Retrofit的呼叫。

所以,我的问题是:

  1. 如何创建一个Rx-Observable对象,它会将List正常发送到Retrofit服务,但是当服务器(或互联网)出现故障时,它应该向DatabaseManager.saveToDb()方法提供未保存的LocationPoints?
  2. 如何捕获互联网连接或服务器“up”状态?创建一些Observable是一个好主意,它会ping我的远程服务器,结果应该为它的订阅者发出一些布尔值?实现此行为的最佳方法是什么?
  3. 当互联网连接(服务器)变为“up”时,是否有一种简单的方法可以使用本地保存的数据(来自本地数据库)对Retrofit调用进行排队?
  4. 如何在服务器端丢失任何LocationPoints? (最后我的客户端应用程序必须发送所有这些!
  5. 我做错了什么?我是Android,Java和新手 特别是对RxJava ......

1 个答案:

答案 0 :(得分:0)

有趣的任务!首先:您不需要创建DB来存储这么小的信息。 Android可以存储任何Serializable数据。 因此,为了保存本地数据包模型,如:

public class MyLocation implements Serializable {

   @Nonnull
   private final String id;
   private final Location location;
   private final boolean isSynced;

   // constructor...
   // getters...
}

Singleton类:

public class UserPreferences {
    private static final String LOCATIONS = "locations";
    @Nonnull
    private final SharedPreferences preferences;
    @Nonnull
    private final Gson gson;
    private final PublishSubject<Object> locationRefresh = PublishSubject.create();

public void addLocation(MyLocation location) {
    final String json = preferences.getString(LOCATIONS, null);

    final Type type = new TypeToken<List<MyLocation>>() {
    }.getType();

    final List<MyLocation> list;
    if (!Strings.isNullOrEmpty(json)) {
        list = gson.fromJson(json, type);
    } else {
        list = new ArrayList<MyLocation>();
    }
    list.add(lication);

    final String newJson = gson.toJson(set);
    preferences.edit().putString(LOCATIONS, newJson).commit();
    locationRefresh.onNext(null);
}

private List<String> getLocations() {
    final String json = preferences.getString(LOCATIONS, null);

    final Type type = new TypeToken<List<MyLocation>>() {
    }.getType();

    final List<MyLocation> list = new ArrayList<MyLocation>();
    if (!Strings.isNullOrEmpty(json)) {
        list.addAll(gson.<List<MyLocation>>fromJson(json, type));
    }

    return list;
}

@Nonnull
public Observable<List<MyLocation>> getLocationsObservable() {
    return Observable
            .defer(new Func0<Observable<List<MyLocation>>>() {
                @Override
                public Observable<List<MyLocation>> call() {
                    return Observable.just(getLocations())
                            .filter(Functions1.isNotNull());
                }
            })
            .compose(MoreOperators.<List<MyLocation>>refresh(locationRefresh));
}

// also You need to create getLocationsObservable() and getLocations() methods but only for not synced Locations.

}

更改:

public interface GpsService { 
    @POST("/v1/savelocationbatch") 
    Observable<SaveResponse> saveLocationBatch(@Body LocationBatch locationBatch); 
}

现在最有趣的......让一切顺利。 RxJava有extention。它有很多“很酷的工具”(btw,来自那里的UserPref类中的MoreOperators),它也有处理改造错误的东西。

因此,假设当Observable saveLocationObservable发出一些东西时,就会发生位置保存。在这种情况下,您的代码如下所示:

final Observable<ResponseOrError<SaveResponse>> responseOrErrorObservable = saveLocationObservable
        .flatMap(new Func1<MyLocation, Observable<ResponseOrError<SaveResponse>>>() {
            @Override
            public Observable<ResponseOrError<SaveResponse>> call(MyLocation myLocation) {
                final LocationBatch locationBatch = LocationBatch.fromMyLocation(myLocation);  // some method to convert local location to requesr one
                return saveLocationBatch(locationBatch)
                        .observeOn(uiScheduler)
                        .subscribeOn(networkScheduler)
                        .compose(ResponseOrError.<SaveResponse>toResponseOrErrorObservable());
            }

        })
        .replay(1)
        .refCount();
final Observable<Throwable> error = responseOrErrorObservable
     .compose(ResponseOrError.<SaveResponse>onlyError())
     .withLatestFrom(saveLocationObservable, Functions2.<MyLocation>secondParam())
     .subscribe(new Action1<MyLocation>() {
            @Override
            public void call(MyLocation myLocation) {
                   // save location to UserPref with flag isSynced=flase
            }
        });
final Observable<UserInfoResponse> success = responseOrErrorObservable
     .compose(ResponseOrError.<SaveResponse>onlySuccess())
     .subscribe(new Action1<SaveResponse>() {
            @Override
            public void call(SaveResponse response) {
                // save location to UserPref with flag isSynced=true
            }
        });