KunMinX 开源的 Jetpack 组件搭配 MVVM 架构又称 Android Architecture Components 架构简称 AAC 架构之 PureMusic 音乐拨放器范例中的 UseCase 介绍。
在众多开源专案里第一次看到有人实践 Uncle Bob 的 Clean Architecture 中的 UseCase 层,非常惊奇,所以想研究一下如何实践 UseCase。
public interface CallBack<R> {
void onSuccess(R response);
default void onError() {
}
}
/**
* Data passed to a request.
*/
public interface RequestValues {
}
/**
* Data received from a request.
*/
public interface ResponseValue {
}
/**
* Interface for schedulers, see {@link UseCaseThreadPoolScheduler}.
*/
public interface UseCaseScheduler {
void execute(Runnable runnable);
<V extends ResponseValue> void notifyResponse(final V response, final CallBack<V> callBack);
<V extends ResponseValue> void onError(final CallBack<V> callBack);
}
/**
* Use cases are the entry points to the domain layer.
*
* @param <Q> the request type
* @param <P> the response type
*/
public abstract class UseCase<Q extends RequestValues,P extends ResponseValue> {
private Q mRequestValues;
private CallBack<P> mCallBack;
public Q getRequestValues() {
return mRequestValues;
}
public void setRequestValues(Q mRequestValues) {
this.mRequestValues = mRequestValues;
}
public CallBack<P> getCallBack() {
return mCallBack;
}
public void setCallBack(CallBack<P> mCallBack) {
this.mCallBack = mCallBack;
}
void run() {
executeUseCase(mRequestValues);
}
protected abstract void executeUseCase(Q requestValues);
}
public class UseCaseHandler {
private static UseCaseHandler INSTANCE;
private final UseCaseScheduler mUseCaseScheduler;
public UseCaseHandler(UseCaseScheduler mUseCaseScheduler) {
this.mUseCaseScheduler = mUseCaseScheduler;
}
public static UseCaseHandler getInstance() {
if (INSTANCE == null) {
INSTANCE = new UseCaseHandler(new UseCaseThreadPoolScheduler());
}
return INSTANCE;
}
public <T extends RequestValues, R extends ResponseValue> void execute(
final UseCase<T, R> useCase, T values, CallBack<R> callBack) {
useCase.setRequestValues(values);
useCase.setCallBack(new UiCallbackWrapper(callBack, this));
}
private <V extends ResponseValue> void notifyResponse(final V response,
final CallBack<V> callBack) {
mUseCaseScheduler.notifyResponse(response, callBack);
}
private <V extends ResponseValue> void notifyError(final CallBack<V> callBack) {
mUseCaseScheduler.onError(callBack);
}
private static final class UiCallbackWrapper<V extends ResponseValue>
implements CallBack<V> {
private final CallBack<V> mCallBack;
private final UseCaseHandler mUseCaseHandler;
public UiCallbackWrapper(CallBack<V> mCallBack, UseCaseHandler mUseCaseHandler) {
this.mCallBack = mCallBack;
this.mUseCaseHandler = mUseCaseHandler;
}
@Override
public void onSuccess(V response) {
mUseCaseHandler.notifyResponse(response, mCallBack);
}
@Override
public void onError() {
mUseCaseHandler.notifyError(mCallBack);
}
}
}
/**
* Executes asynchronous tasks using a {@link ThreadPoolExecutor}.
* <p>
* See also {@link Executors} for a list of factory methods to create common
* {@link java.util.concurrent.ExecutorService}s for different scenarios.
*/
public class UseCaseThreadPoolScheduler implements UseCaseScheduler {
public static final int POOL_SIZE = 2;
public static final int MAX_POOL_SIZE = 4 * 2;
public static final int FIXED_POOL_SIZE = 4;
public static final int TIMEOUT = 30;
final ThreadPoolExecutor mThreadPoolExecutor;
private final Handler mHandler = new Handler();
public UseCaseThreadPoolScheduler() {
mThreadPoolExecutor = new ThreadPoolExecutor(
FIXED_POOL_SIZE, FIXED_POOL_SIZE,
TIMEOUT, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
}
@Override
public void execute(Runnable runnable) {
mThreadPoolExecutor.execute(runnable);
}
@Override
public <V extends ResponseValue> void notifyResponse(V response, CallBack<V> callBack) {
mHandler.post(() -> {
if (callBack != null) {
callBack.onSuccess(response);
}
});
}
@Override
public <V extends ResponseValue> void onError(CallBack<V> callBack) {
mHandler.post(callBack::onError);
}
}
/**
* UseCase 示例,实现 LifeCycle 接口,单独服务於 有 “叫停” 需求 的业务
* 同样是“下载”,我不是在数据层分别写两个方法,
* 而是遵循开闭原则,在 ViewModel 和 数据层之间,插入一个 UseCase,来专门负责可叫停的情况,
* 除了开闭原则,使用 UseCase 还有个考虑就是避免内存泄漏,
*/
public class CanBeStoppedUseCase extends UseCase<CanBeStoppedUseCase.CanBeStoppedRequestValues,
CanBeStoppedUseCase.CanBeStoppedResponseValue> implements DefaultLifecycleObserver {
private final DownloadFile mDownloadFile = new DownloadFile();
@Override
public void onStop(@NonNull LifecycleOwner owner) {
if (getRequestValues() != null) {
mDownloadFile.setForgive(true);
mDownloadFile.setProgress(0);
mDownloadFile.setFile(null);
getCallBack().onError();
}
}
@Override
protected void executeUseCase(CanBeStoppedRequestValues canBeStoppedRequestValues) {
//访问数据层资源,在 UseCase 中处理带叫停性质的业务
DataRepository.getInstance().downloadFile(mDownloadFile, dataResult -> {
getCallBack().onSuccess(new CanBeStoppedResponseValue(dataResult));
});
}
public static final class CanBeStoppedRequestValues implements RequestValues {
}
public static final class CanBeStoppedResponseValue implements ResponseValue {
private final DataResult<DownloadFile> mDataResult;
public CanBeStoppedResponseValue(DataResult<DownloadFile> dataResult) {
mDataResult = dataResult;
}
public DataResult<DownloadFile> getDataResult() {
return mDataResult;
}
}
}
public class DownloadUseCase extends UseCase<DownloadUseCase.DownloadRequestValues, DownloadUseCase.DownloadResponseValue> {
@Override
protected void executeUseCase(DownloadRequestValues requestValues) {
try {
URL url = new URL(requestValues.url);
InputStream is = url.openStream();
File file = new File(Configs.COVER_PATH, requestValues.path);
OutputStream os = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) > 0) {
os.write(buffer, 0, len);
}
is.close();
os.close();
getCallBack().onSuccess(new DownloadResponseValue(file));
} catch (IOException e) {
e.printStackTrace();
}
}
public static final class DownloadRequestValues implements RequestValues {
private String url;
private String path;
public DownloadRequestValues(String url, String path) {
this.url = url;
this.path = path;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
public static final class DownloadResponseValue implements ResponseValue {
private File mFile;
public DownloadResponseValue(File file) {
mFile = file;
}
public File getFile() {
return mFile;
}
public void setFile(File file) {
mFile = file;
}
}
}
/**
* 数据下载 Request
* <p>
* TODO tip 1:Request 通常按业务划分
* 一个项目中通常存在多个 Request 类,
* 每个页面配备的 state-ViewModel 实例可根据业务需要持有多个不同的 Request 实例。
* <p>
* request 的职责仅限於 "业务逻辑处理" 和 "Event 分发",不建议在此处理 UI 逻辑,
* UI 逻辑只适合在 Activity/Fragment 等视图控制器中完成,是 “数据驱动” 的一部分,
* 将来升级到 Jetpack Compose 更是如此。
* <p>
*/
public class DownloadRequest extends BaseRequest {
private final UnPeekLiveData<DataResult<DownloadFile>> mDownloadFileLiveData = new UnPeekLiveData<>();
private final UnPeekLiveData<DataResult<DownloadFile>> mDownloadFileCanBeStoppedLiveData = new UnPeekLiveData<>();
private final CanBeStoppedUseCase mCanBeStoppedUseCase = new CanBeStoppedUseCase();
public ProtectedUnPeekLiveData<DataResult<DownloadFile>> getDownloadFileLiveData() {
return mDownloadFileLiveData;
}
public ProtectedUnPeekLiveData<DataResult<DownloadFile>> getDownloadFileCanBeStoppedLiveData() {
return mDownloadFileCanBeStoppedLiveData;
}
public CanBeStoppedUseCase getCanBeStoppedUseCase() {
return mCanBeStoppedUseCase;
}
public void requestDownloadFile() {
DownloadFile downloadFile = new DownloadFile();
DataRepository.getInstance().downloadFile(downloadFile, mDownloadFileLiveData::postValue);
}
public void requestCanBeStoppedDownloadFile() {
UseCaseHandler.getInstance().execute(getCanBeStoppedUseCase(),
new CanBeStoppedUseCase.RequestValues(), response -> {
mDownloadFileCanBeStoppedLiveData.setValue(response.getDataResult());
});
}
}
/**
* 每个页面都要单独准备一个 state-ViewModel,
* 来托管 DataBinding 绑定的临时状态,以及视图控制器重建时状态的恢复。
* <p>
* 此外,state-ViewModel 的职责仅限於 状态托管,不建议在此处理 UI 逻辑,
* UI 逻辑只适合在 Activity/Fragment 等视图控制器中完成,是 “数据驱动” 的一部分,
* 将来升级到 Jetpack Compose 更是如此。
*/
public class SearchViewModel extends ViewModel {
public final ObservableField<Integer> progress = new ObservableField<>();
public final ObservableField<Integer> progress_cancelable = new ObservableField<>();
public final DownloadRequest downloadRequest = new DownloadRequest();
}
ThreadPoolExecutor
来做 UseCase 的排程。让我们再看一下 Clean Architecture
根据 Uncle Bob 的 Clean Architecture 文章表示
Use Cases
The software in this layer contains application specific business rules. It encapsulates and implements all of the use cases of the system. These use cases orchestrate the flow of data to and from the entities, and direct those entities to use their enterprise wide business rules to achieve the goals of the use case.
We do not expect changes in this layer to affect the entities. We also do not expect this layer to be affected by changes to externalities such as the database, the UI, or any of the common frameworks. This layer is isolated from such concerns.
We do, however, expect that changes to the operation of the application will affect the use-cases and therefore the software in this layer. If the details of a use-case change, then some code in this layer will certainly be affected.
Pros:
Cons:
Architecture Pattern
Clean Architecture
Domain Layer
UseCase
MVVM
Jetpack
<<: MacOS读取蓝牙摇杆讯号,利用python修改pynput程序码实现 - 3.修改pynput
>>: 2022新年挑战 - 7 days for Javascript(Day 1 - Developer Set Up)
前言 Hi,铁人赛第二天,跟大家聊聊沟通,预计会陆续写几篇相关的主题,今天来分享平时的观察。 在敏捷...
线性的资料储存方式一般有两种 array (阵列) list node (链结) 这两种差别到底在那...
笔者在这几个事件的时间线上有点错乱,但没关系,大家当故事听听就好了 XDDD 还记得我第一次参加 I...
要操控google 的档案如google drive, google sheet, ...等 你除了...
本周的目标是要让横向卷轴中的角色可以左右移动及跳跃, 在没有碰到场景物件时自由落体, 碰到墙壁时被...