一. what ?
  对于一个框架来说, 用户只需要知道这个框架的关键组件和接口行了, 不要对外公布太多的细节. 因为用户看到的东西太多反而导致了迷惑. 对于用户来说, 只要调用一个方法帮我完成我想要的那些复杂功能, 这样好不过了. 接口和实现分开或者说只对外公布用户要使用的接口, 而其实现则对用户隐藏起来. 这是一个框架应该做的事情, 也是Java的一个重要特性 ------ 封装. 简单的来说接口和实现的分离是把接口已实现分开, 尽量减少两者之间的依赖, 以方便移植和修改. 那么隐藏实现又怎么说呢? 前面已经说了, 一个框架要做到的是尽量不要公布实现, 只公布接口. 因此需要对实现进行封装并隐藏. 这样说有些抽象, 你可能有些不知所云. 下面我将说说为什要进行接口和实现的分离、对实现方式进行隐藏 以及怎么实现它们.
  二. why ?
  在《在Android上使用SPI机制》一文中已经说过关于接口和实现的分离和动态更换实现的问题, 但是接口的实现并未对外隐藏, 用户可以直接调用接口的实现, 而不使用接口. 这样编写的代码并不是面向接口编程, 而是硬编码. 如果要更换实现, 这将非常麻烦. 为了不让用户看到实现, 只需要将实现类变成包私有的类用反射初始化. 分离接口和实现以及隐藏实现细节好处很多, 下面列举几个:
  面向接口编程, 方便更换实现.
  隐藏实现细节, 减少对外接口和类.
  减少接口和实现直接的相互依赖.
  封装, 高内聚.
  ......
  三. how ?
  下面看看如何实现:
  (1) 定义接口
public interface ImageLoader {
/**
* 初始化ImageLoader
* @param appContext ApplicatonContext
*/
void init(@NonNull Context appContext);
/**
* 展示图片
* @param targetView
* @param uri
* @param listener
*/
void displayImage(@NonNull ImageView targetView, @NonNull Uri uri, @Nullable LoadListener listener);
/**
* 取消图片展示
* @param targetView
*/
void cancelDisplay(ImageView targetView);
/**
* 销毁ImageLoader, 回收资源
*/
void destroy();
}
  (2) 实现接口 (实现类要定义成包私有的, 即没有修饰符)
  FrescoImageLoader.java文件:
class FrescoImageLoader implements ImageLoader {
private Context mAppContext;
@Override
public void init(@NonNull Context appContext) {
if(Fresco.hasBeenInitialized()) return;
// hold appContext
mAppContext = appContext;
// init fresco
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(chain -> {
DevUtil.d("ImageLoader", "request-url: " + chain.request().url().toString());
return chain.proceed(chain.request());
})
.build();
ImagePipelineConfig config = OkHttpImagePipelineConfigFactory
.newBuilder(appContext, client)
.build();
Fresco.initialize(appContext, config);
}
@Override
public void displayImage(@NonNull ImageView targetView, @NonNull Uri uri, @Nullable LoadListener listener) {
// Fresco
if (targetView instanceof DraweeView) {
DraweeView realView = (DraweeView) targetView;
realView.setController(getDraweeController(realView, uri, listener));
return;
}
// Generic ImageView
targetView.setImageURI(uri);
}
private DraweeController getDraweeController(DraweeView targetView, Uri uri, LoadListener listener) {
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(targetView.getController())
.setUri(uri)
.setControllerListener(listener == null ? null : new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(String id, Throwable throwable) {
super.onFailure(id, throwable);
if (listener != null) {
listener.onFailed(targetView);
}
}
@Override
public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
super.onFinalImageSet(id, imageInfo, animatable);
if (imageInfo instanceof CloseableBitmap) {
CloseableBitmap image = (CloseableBitmap) imageInfo;
Bitmap resultBitmap = image.getUnderlyingBitmap();
if (listener != null) {
listener.onSuccess(targetView, resultBitmap);
}
}
}
})
.build();
return controller;
}
@Override
public void cancelDisplay(ImageView targetView) {
}
@Override
public void destroy() {
Fresco.shutDown();
}
}
  UILImageLoader.java文件
class UILImageLoader implements ImageLoader {
private com.nostra13.universalimageloader.core.ImageLoader mImpl;
@Override
public void init(@NonNull Context appContext) {
mImpl = com.nostra13.universalimageloader.core.ImageLoader.getInstance();
}
@Override
public void displayImage(@NonNull ImageView targetView, @NonNull Uri uri, @Nullable LoadListener listener) {
com.nostra13.universalimageloader.core.ImageLoader.getInstance().displayImage(uri.toString(), targetView);
}
@Override
public void cancelDisplay(ImageView targetView) {
mImpl.cancelDisplayTask(targetView);
}
@Override
public void destroy() {
mImpl.destroy();
}
}
  其余省略................