mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-22 20:18:36 +00:00
Improve conversation open speed.
Co-authored-by: Cody Henthorne <cody@signal.org>
This commit is contained in:
committed by
Cody Henthorne
parent
d3049a3433
commit
666218773c
@@ -1,9 +1,7 @@
|
||||
package org.signal.paging;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@@ -24,16 +22,19 @@ class BufferedPagingController<Key, Data> implements PagingController<Key> {
|
||||
|
||||
private final PagedDataSource<Key, Data> dataSource;
|
||||
private final PagingConfig config;
|
||||
private final MutableLiveData<List<Data>> liveData;
|
||||
private final DataStream<Data> dataStream;
|
||||
private final Executor serializationExecutor;
|
||||
|
||||
private PagingController<Key> activeController;
|
||||
private int lastRequestedIndex;
|
||||
|
||||
BufferedPagingController(PagedDataSource<Key, Data> dataSource, PagingConfig config, @NonNull MutableLiveData<List<Data>> liveData) {
|
||||
BufferedPagingController(@NonNull PagedDataSource<Key, Data> dataSource,
|
||||
@NonNull PagingConfig config,
|
||||
@NonNull DataStream<Data> dataStream)
|
||||
{
|
||||
this.dataSource = dataSource;
|
||||
this.config = config;
|
||||
this.liveData = liveData;
|
||||
this.dataStream = dataStream;
|
||||
this.serializationExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
this.activeController = null;
|
||||
@@ -57,7 +58,7 @@ class BufferedPagingController<Key, Data> implements PagingController<Key> {
|
||||
activeController.onDataInvalidated();
|
||||
}
|
||||
|
||||
activeController = new FixedSizePagingController<>(dataSource, config, liveData, dataSource.size());
|
||||
activeController = new FixedSizePagingController<>(dataSource, config, dataStream, dataSource.size());
|
||||
activeController.onDataNeededAroundIndex(lastRequestedIndex);
|
||||
});
|
||||
}
|
||||
|
||||
10
paging/lib/src/main/java/org/signal/paging/DataStream.java
Normal file
10
paging/lib/src/main/java/org/signal/paging/DataStream.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package org.signal.paging;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An abstraction over different types of ways the paging lib can provide data, e.g. Observables vs LiveData.
|
||||
*/
|
||||
interface DataStream<Data> {
|
||||
void next(List<Data> data);
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.signal.paging;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
@@ -29,7 +28,7 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
|
||||
private final PagedDataSource<Key, Data> dataSource;
|
||||
private final PagingConfig config;
|
||||
private final MutableLiveData<List<Data>> liveData;
|
||||
private final DataStream<Data> dataStream;
|
||||
private final DataStatus loadState;
|
||||
private final Map<Key, Integer> keyToPosition;
|
||||
|
||||
@@ -39,15 +38,17 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
|
||||
FixedSizePagingController(@NonNull PagedDataSource<Key, Data> dataSource,
|
||||
@NonNull PagingConfig config,
|
||||
@NonNull MutableLiveData<List<Data>> liveData,
|
||||
@NonNull DataStream<Data> dataStream,
|
||||
int size)
|
||||
{
|
||||
this.dataSource = dataSource;
|
||||
this.config = config;
|
||||
this.liveData = liveData;
|
||||
this.dataStream = dataStream;
|
||||
this.loadState = DataStatus.obtain(size);
|
||||
this.data = new CompressedList<>(loadState.size());
|
||||
this.keyToPosition = new HashMap<>();
|
||||
|
||||
if (DEBUG) Log.d(TAG, "[Constructor] Creating with size " + size + " (loadState.size() = " + loadState.size() + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +59,7 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
@Override
|
||||
public void onDataNeededAroundIndex(int aroundIndex) {
|
||||
if (invalidated) {
|
||||
Log.w(TAG, buildLog(aroundIndex, "Invalidated! At very beginning."));
|
||||
Log.w(TAG, buildDataNeededLog(aroundIndex, "Invalidated! At very beginning."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
|
||||
synchronized (loadState) {
|
||||
if (loadState.size() == 0) {
|
||||
liveData.postValue(Collections.emptyList());
|
||||
dataStream.next(Collections.emptyList());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,14 +82,14 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
loadStart = loadState.getEarliestUnmarkedIndexInRange(leftLoadBoundary, rightLoadBoundary);
|
||||
|
||||
if (loadStart < 0) {
|
||||
if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "loadStart < 0"));
|
||||
if (DEBUG) Log.i(TAG, buildDataNeededLog(aroundIndex, "loadStart < 0"));
|
||||
return;
|
||||
}
|
||||
|
||||
loadEnd = loadState.getLatestUnmarkedIndexInRange(Math.max(leftLoadBoundary, loadStart), rightLoadBoundary) + 1;
|
||||
|
||||
if (loadEnd <= loadStart) {
|
||||
if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "loadEnd <= loadStart, loadEnd: " + loadEnd + ", loadStart: " + loadStart));
|
||||
if (DEBUG) Log.i(TAG, buildDataNeededLog(aroundIndex, "loadEnd <= loadStart, loadEnd: " + loadEnd + ", loadStart: " + loadStart));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,19 +97,19 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
|
||||
loadState.markRange(loadStart, loadEnd);
|
||||
|
||||
if (DEBUG) Log.i(TAG, buildLog(aroundIndex, "start: " + loadStart + ", end: " + loadEnd + ", totalSize: " + totalSize));
|
||||
if (DEBUG) Log.i(TAG, buildDataNeededLog(aroundIndex, "start: " + loadStart + ", end: " + loadEnd + ", totalSize: " + totalSize));
|
||||
}
|
||||
|
||||
FETCH_EXECUTOR.execute(() -> {
|
||||
if (invalidated) {
|
||||
Log.w(TAG, buildLog(aroundIndex, "Invalidated! At beginning of load task."));
|
||||
Log.w(TAG, buildDataNeededLog(aroundIndex, "Invalidated! At beginning of load task."));
|
||||
return;
|
||||
}
|
||||
|
||||
List<Data> loaded = dataSource.load(loadStart, loadEnd - loadStart, () -> invalidated);
|
||||
|
||||
if (invalidated) {
|
||||
Log.w(TAG, buildLog(aroundIndex, "Invalidated! Just after data was loaded."));
|
||||
Log.w(TAG, buildDataNeededLog(aroundIndex, "Invalidated! Just after data was loaded."));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -123,7 +124,7 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
}
|
||||
|
||||
data = updated;
|
||||
liveData.postValue(updated);
|
||||
dataStream.next(updated);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -139,6 +140,8 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
|
||||
@Override
|
||||
public void onDataItemChanged(Key key) {
|
||||
if (DEBUG) Log.d(TAG, buildItemChangedLog(key, ""));
|
||||
|
||||
FETCH_EXECUTOR.execute(() -> {
|
||||
Integer position = keyToPosition.get(key);
|
||||
|
||||
@@ -172,12 +175,16 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
|
||||
updatedList.set(position, item);
|
||||
data = updatedList;
|
||||
liveData.postValue(updatedList);
|
||||
dataStream.next(updatedList);
|
||||
|
||||
if (DEBUG) Log.d(TAG, buildItemChangedLog(key, "Published updated data"));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataItemInserted(Key key, int position) {
|
||||
if (DEBUG) Log.d(TAG, buildItemInsertedLog(key, position, ""));
|
||||
|
||||
FETCH_EXECUTOR.execute(() -> {
|
||||
if (keyToPosition.containsKey(key)) {
|
||||
Log.w(TAG, "Notified of key " + key + " being inserted at " + position + ", but the item already exists!");
|
||||
@@ -191,6 +198,7 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
|
||||
synchronized (loadState) {
|
||||
loadState.insertState(position, true);
|
||||
if (DEBUG) Log.d(TAG, buildItemInsertedLog(key, position, "Size of loadState updated to " + loadState.size()));
|
||||
}
|
||||
|
||||
Data item = dataSource.load(key);
|
||||
@@ -211,7 +219,9 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
rebuildKeyToPositionMap(keyToPosition, updatedList, dataSource);
|
||||
|
||||
data = updatedList;
|
||||
liveData.postValue(updatedList);
|
||||
dataStream.next(updatedList);
|
||||
|
||||
if (DEBUG) Log.d(TAG, buildItemInsertedLog(key, position, "Published updated data"));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -226,7 +236,15 @@ class FixedSizePagingController<Key, Data> implements PagingController<Key> {
|
||||
}
|
||||
}
|
||||
|
||||
private static String buildLog(int aroundIndex, String message) {
|
||||
return "onDataNeededAroundIndex(" + aroundIndex + ") " + message;
|
||||
private String buildDataNeededLog(int aroundIndex, String message) {
|
||||
return "[onDataNeededAroundIndex(" + aroundIndex + "), size: " + loadState.size() + "] " + message;
|
||||
}
|
||||
|
||||
private String buildItemInsertedLog(Key key, int position, String message) {
|
||||
return "[onDataItemInserted(" + key + ", " + position + "), size: " + loadState.size() + "] " + message;
|
||||
}
|
||||
|
||||
private String buildItemChangedLog(Key key, String message) {
|
||||
return "[onDataItemInserted(" + key + "), size: " + loadState.size() + "] " + message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.signal.paging;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An implementation of {@link PagedData} that will provide data as a {@link LiveData}.
|
||||
*/
|
||||
public class LivePagedData<Key, Data> extends PagedData<Key> {
|
||||
|
||||
private final LiveData<List<Data>> data;
|
||||
|
||||
LivePagedData(@NonNull LiveData<List<Data>> data, @NonNull PagingController<Key> controller) {
|
||||
super(controller);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public @NonNull LiveData<List<Data>> getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.signal.paging;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.subjects.Subject;
|
||||
|
||||
/**
|
||||
* An implementation of {@link PagedData} that will provide data as an {@link Observable}.
|
||||
*/
|
||||
public class ObservablePagedData<Key, Data> extends PagedData<Key> {
|
||||
|
||||
private final Observable<List<Data>> data;
|
||||
|
||||
ObservablePagedData(@NonNull Observable<List<Data>> data, @NonNull PagingController<Key> controller) {
|
||||
super(controller);
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public @NonNull Observable<List<Data>> getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -2,39 +2,41 @@ package org.signal.paging;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject;
|
||||
import io.reactivex.rxjava3.subjects.Subject;
|
||||
|
||||
/**
|
||||
* The primary entry point for creating paged data.
|
||||
*/
|
||||
public final class PagedData<Key, Data> {
|
||||
public class PagedData<Key> {
|
||||
|
||||
private final LiveData<List<Data>> data;
|
||||
private final PagingController<Key> controller;
|
||||
|
||||
@AnyThread
|
||||
public static <Key, Data> PagedData<Key, Data> create(@NonNull PagedDataSource<Key, Data> dataSource, @NonNull PagingConfig config) {
|
||||
MutableLiveData<List<Data>> liveData = new MutableLiveData<>();
|
||||
PagingController<Key> controller = new BufferedPagingController<>(dataSource, config, liveData);
|
||||
|
||||
return new PagedData<>(liveData, controller);
|
||||
}
|
||||
|
||||
private PagedData(@NonNull LiveData<List<Data>> data, @NonNull PagingController<Key> controller) {
|
||||
this.data = data;
|
||||
protected PagedData(PagingController<Key> controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public @NonNull LiveData<List<Data>> getData() {
|
||||
return data;
|
||||
public static <Key, Data> LivePagedData<Key, Data> createForLiveData(@NonNull PagedDataSource<Key, Data> dataSource, @NonNull PagingConfig config) {
|
||||
MutableLiveData<List<Data>> liveData = new MutableLiveData<>();
|
||||
PagingController<Key> controller = new BufferedPagingController<>(dataSource, config, liveData::postValue);
|
||||
|
||||
return new LivePagedData<>(liveData, controller);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public @NonNull PagingController<Key> getController() {
|
||||
public static <Key, Data> ObservablePagedData<Key, Data> createForObservable(@NonNull PagedDataSource<Key, Data> dataSource, @NonNull PagingConfig config) {
|
||||
Subject<List<Data>> subject = BehaviorSubject.create();
|
||||
PagingController<Key> controller = new BufferedPagingController<>(dataSource, config, subject::onNext);
|
||||
|
||||
return new ObservablePagedData<>(subject, controller);
|
||||
}
|
||||
|
||||
public PagingController<Key> getController() {
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user