Glide源码阅读之模板模式2

续接《Glide设计模式之模板模式1》

LifecycleListener已知的实现类列表如下:

  1. AppWidgetTarget
  2. BaseTarget
  3. BitmapImageViewTarget
  4. BitmapThumbnailImageViewTarget
  5. CustomTarget
  6. CustomViewTarget
  7. DrawableImageViewTarget
  8. DrawableThumbnailImageViewTarget
  9. ImageViewTarget
  10. NotificationTarget
  11. PreloadTarget
  12. RequestFutureTarget
  13. RequestManager
  14. SimpleTarget
  15. TargetTracker
  16. ThumbnailImageViewTarget
  17. ViewTarget

DrawableThumbnailImageViewTarget

com.bumptech.glide.request.target.DrawableThumbnailImageViewTarget
高效地显示多个连续加载到一个视图中的Drawables。


/**
 * Efficiently displays multiple Drawables loaded serially into a single {@link android.view.View}.
 */
 高效地显示多个连续加载到一个视图中的Drawables// Public API.
@SuppressWarnings("unused")
public class DrawableThumbnailImageViewTarget extends ThumbnailImageViewTarget<Drawable> {
  public DrawableThumbnailImageViewTarget(ImageView view) {
    super(view);
  }

  /** @deprecated Use {@link #waitForLayout()} instead. */
  @Deprecated
  @SuppressWarnings("deprecation")
  public DrawableThumbnailImageViewTarget(ImageView view, boolean waitForLayout) {
    super(view, waitForLayout);
  }

  @Override
  protected Drawable getDrawable(Drawable resource) {
    return resource;
  }
}

NotificationTarget

com.bumptech.glide.request.target.NotificationTarget
这个类用于通过RemoteViews在一个通知的ImageView中显示下载的位图。
注意:为了使取消正常工作,您必须在每次后续加载时传入这个类的相同实例。

/**
 * This class is used to display downloaded Bitmap inside an ImageView of a Notification through
 * RemoteViews.
 *
 * <p>Note - For cancellation to work correctly, you must pass in the same instance of this class
 * for every subsequent load.
 */
 这个类用于通过RemoteViews在一个通知的ImageView中显示下载的位图。
注意:为了使取消正常工作,您必须在每次后续加载时传入这个类的相同实例。
// Public API.
@SuppressWarnings({"WeakerAccess", "unused"})
public class NotificationTarget extends CustomTarget<Bitmap> {

PreloadTarget

com.bumptech.glide.request.target.PreloadTarget
一次性使用的Target类,它将资源加载到内存中,然后清除自己。


/**
 * A one time use {@link com.bumptech.glide.request.target.Target} class that loads a resource into
 * memory and then clears itself.
 *
 * @param <Z> The type of resource that will be loaded into memory.
 */
 一次性使用的Target类,它将资源加载到内存中,然后清除自己
public final class PreloadTarget<Z> extends CustomTarget<Z> {
  private static final int MESSAGE_CLEAR = 1;
  private static final Handler HANDLER =
      new Handler(
          Looper.getMainLooper(),
          new Callback() {
            @Override
            public boolean handleMessage(Message message) {
              if (message.what == MESSAGE_CLEAR) {
                ((PreloadTarget<?>) message.obj).clear();
                return true;
              }
              return false;
            }
          });

  private final RequestManager requestManager;

  /**
   * Returns a PreloadTarget.
   *
   * @param width The width in pixels of the desired resource.
   * @param height The height in pixels of the desired resource.
   * @param <Z> The type of the desired resource.
   */
  public static <Z> PreloadTarget<Z> obtain(RequestManager requestManager, int width, int height) {
    return new PreloadTarget<>(requestManager, width, height);
  }

  private PreloadTarget(RequestManager requestManager, int width, int height) {
    super(width, height);
    this.requestManager = requestManager;
  }

  @Override
  public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
    // If a thumbnail request is set and the thumbnail completes, we don't want to cancel the
    // primary load. Instead we wait until the primary request (the one set on the target) says
    // that it is complete.
    // Note - Any thumbnail request that does not complete before the primary request will be
    // cancelled and may not be preloaded successfully. Cancellation of outstanding thumbnails after
    // the primary request succeeds is a common behavior of all Glide requests and we're not trying
    // to override it here.
    Request request = getRequest();
    if (request != null && request.isComplete()) {
      HANDLER.obtainMessage(MESSAGE_CLEAR, this).sendToTarget();
    }
  }

  @Override
  public void onLoadCleared(@Nullable Drawable placeholder) {
    // Do nothing, we don't retain a reference to our resource.
  }

  @SuppressWarnings("WeakerAccess")
  @Synthetic
  void clear() {
    requestManager.clear(this);
  }
}

RequestFutureTarget

com.bumptech.glide.request.RequestFutureTarget
一个未来的Glide实现,可用于在后台线程以阻塞方式加载资源

/**
 * A {@link java.util.concurrent.Future} implementation for Glide that can be used to load resources
 * in a blocking manner on background threads.
 *
 * <p>Note - Unlike most targets, RequestFutureTargets can be used once and only once. Attempting to
 * reuse a RequestFutureTarget will probably result in undesirable behavior or exceptions. Instead
 * of reusing objects of this class, the pattern should be:
 *
 * <pre>{@code
 * FutureTarget<File> target = null;
 * RequestManager requestManager = Glide.with(context);
 * try {
 *   target = requestManager
 *      .downloadOnly()
 *      .load(model)
 *      .submit();
 *   File downloadedFile = target.get();
 *   // ... do something with the file (usually throws IOException)
 * } catch (ExecutionException | InterruptedException | IOException e) {
 *   // ... bug reporting or recovery
 * } finally {
 *   // make sure to cancel pending operations and free resources
 *   if (target != null) {
 *     target.cancel(true); // mayInterruptIfRunning
 *   }
 * }
 * }</pre>
 *
 * The {@link #cancel(boolean)} call will cancel pending operations and make sure that any resources
 * used are recycled.
 *
 * @param <R> The type of the resource that will be loaded.
 */
 一个未来的Glide实现,可用于在后台线程以阻塞方式加载资源。
注意:与大多数目标不同,RequestFutureTargets只能被使用一次。试图重用RequestFutureTarget可能会导致不良的行为或异常。与其重用这个类的对象,模式应该是:
FutureTarget<文件>目标= null;
RequestManager RequestManager = Glide.with(context);
尝试{
目标= requestManager
.downloadOnly ()
.load(模型)
。submit ();
filedownloaddfile = target.get();
/ /……对文件做一些事情(通常抛出IOException)
} catch (ExecutionException | InterruptedException | IOException e) {
/ /……Bug报告或修复
最后}{
//确保取消挂起的操作和释放资源
If (target != null) {
target.cancel(真正的);/ / mayInterruptIfRunning
}
}

cancel(boolean)调用将取消挂起的操作,并确保所有使用的资源都被回收。
public class RequestFutureTarget<R> implements FutureTarget<R>, RequestListener<R> {
 

FutureTarget

com.bumptech.glide.request.FutureTarget
既是目标又是未来的对象的接口


/**
 * An interface for an object that is both a {@link com.bumptech.glide.request.target.Target} and a
 * {@link java.util.concurrent.Future}. For example:
 *
 * <pre>{@code
 * FutureTarget<Bitmap> futureTarget = Glide.with(fragment)
 *                                       .load("http://goo.gl/1asf12")
 *                                       .asBitmap()
 *                                       .into(250, 250);
 * Bitmap myBitmap = futureTarget.get();
 * ... // do things with bitmap and then release when finished:
 * futureTarget.cancel(false);
 * }</pre>
 *
 * <p>Note - {@link #get()} and {@link #get(long, java.util.concurrent.TimeUnit)} must be called off
 * of the main thread or they will block forever.
 *
 * @param <R> The type of resource this FutureTarget will retrieve.
 */
 既是目标又是未来的对象的接口。例如:

FutureTarget<Bitmap> FutureTarget = Glide.with(fragment)
.load(“http://goo.gl/1asf12”)
.asBitmap ()
.into(250250);
Bitmap myBitmap = futureTarget.get();.//使用bitmap做事情,然后在完成时释放:
futureTarget.cancel();

注意- Future.get()Futureget(long, java.util.concurrent.TimeUnit)必须从主线程中调用,否则它们将永远阻塞。
public interface FutureTarget<R> extends Future<R>, Target<R> {}

RequestManager

com.bumptech.glide.RequestManager
用于管理和启动Glide请求的类。可以使用活动、片段和连接生命周期事件来智能地停止、启动和重新启动请求。通过实例化一个新对象来检索,或者利用内置的Activity和Fragment生命周期处理,使用静态Glide。用Fragment或Activity加载方法。


/**
 * A class for managing and starting requests for Glide. Can use activity, fragment and connectivity
 * lifecycle events to intelligently stop, start, and restart requests. Retrieve either by
 * instantiating a new object, or to take advantage built in Activity and Fragment lifecycle
 * handling, use the static Glide.load methods with your Fragment or Activity.
 *
 * @see Glide#with(android.app.Activity)
 * @see Glide#with(androidx.fragment.app.FragmentActivity)
 * @see Glide#with(android.app.Fragment)
 * @see Glide#with(androidx.fragment.app.Fragment)
 * @see Glide#with(Context)
 */
 用于管理和启动Glide请求的类。可以使用活动、片段和连接生命周期事件来智能地停止、启动和重新启动请求。通过实例化一个新对象来检索,或者利用内置的ActivityFragment生命周期处理,使用静态Glide。用FragmentActivity加载方法。
public class RequestManager
    implements ComponentCallbacks2, LifecycleListener, ModelTypes<RequestBuilder<Drawable>> {
  private static final RequestOptions DECODE_TYPE_BITMAP = decodeTypeOf(Bitmap.class).lock();
  private static final RequestOptions DECODE_TYPE_GIF = decodeTypeOf(GifDrawable.class).lock();
  private static final RequestOptions DOWNLOAD_ONLY_OPTIONS =
      diskCacheStrategyOf(DiskCacheStrategy.DATA).priority(Priority.LOW).skipMemoryCache(true);

  protected final Glide glide;
  protected final Context context;

  @SuppressWarnings("WeakerAccess")
  @Synthetic
  final Lifecycle lifecycle;
   @GuardedBy("this")
  private final RequestTracker requestTracker;

  @GuardedBy("this")
  private final RequestManagerTreeNode treeNode;

  @GuardedBy("this")
  private final TargetTracker targetTracker = new TargetTracker();

 private final Runnable addSelfToLifecycle =
      new Runnable() {
        @Override
        public void run() {
          lifecycle.addListener(RequestManager.this);
        }
      };


  /**
   * Lifecycle callback that registers for connectivity events (if the
   * android.permission.ACCESS_NETWORK_STATE permission is present) and restarts failed or paused
   * requests.
   */
  @Override
  public synchronized void onStart() {
    resumeRequests();
    targetTracker.onStart();
  }

  /**
   * Lifecycle callback that unregisters for connectivity events (if the
   * android.permission.ACCESS_NETWORK_STATE permission is present) and pauses in progress loads.
   */
  @Override
  public synchronized void onStop() {
    pauseRequests();
    targetTracker.onStop();
  }

  /**
   * Lifecycle callback that cancels all in progress requests and clears and recycles resources for
   * all completed requests.
   */
  @Override
  public synchronized void onDestroy() {
    targetTracker.onDestroy();
    for (Target<?> target : targetTracker.getAll()) {
      clear(target);
    }
    targetTracker.clear();
    requestTracker.clearRequests();
    lifecycle.removeListener(this);
    lifecycle.removeListener(connectivityMonitor);
    Util.removeCallbacksOnUiThread(addSelfToLifecycle);
    glide.unregisterRequestManager(this);
  }


RequestManager是一个比较复杂的类。涉及的业务逻辑比较多,本文只关注从接口和抽象类集成过了后实现的部分,专注模板模式的业务实现。便于理解

SimpleTarget

com.bumptech.glide.request.target.SimpleTarget
一个简单的Target基类,具有非基本方法的默认(通常为no-op)实现,允许调用者指定确切的宽度/高度。
已弃用


/**
 * A simple {@link com.bumptech.glide.request.target.Target} base class with default (usually no-op)
 * implementations of non essential methods that allows the caller to specify an exact width/height.
 * Typically use cases look something like this:
 *
 * <pre>
 * <code>
 * Target<Bitmap> target =
 *     Glide.with(fragment)
 *       .asBitmap()
 *       .load("http://somefakeurl.com/fakeImage.jpeg")
 *       .apply(fitCenterTransform())
 *       .into(new SimpleTarget<Bitmap>(250, 250) {
 *
 *         {@literal @Override}
 *         public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
 *           // Do something with bitmap here.
 *         }
 *
 *       });
 * }
 * // At some later point, clear the Target to release the resources, prevent load queues from
 * // blowing out proportion, and to improve load times for future requests:
 * Glide.with(fragment).clear(target);
 * </code>
 * </pre>
 *
 * <p><em>Warning!</em> this class is extremely prone to mis-use. Use SimpleTarget only as a last
 * resort. {@link ViewTarget} or a subclass of {@link ViewTarget} is almost always a better choice.
 *
 * <p><em>Don't forget to clear instances of this class!</em>. If you must use this class, keep in
 * mind that unlike {@link ViewTarget} it is not safe to load into new instances of this class
 * repeatedly if every instance updates the same underlying {@link View} or caller. If you need to
 * load into the same {@link View} or caller repeatedly using this class, always retain a reference
 * to the previous instance and either call {@link com.bumptech.glide.RequestManager#clear(Target)}
 * on the old instance before starting a new load or you must re-use the old instance for the new
 * load. Glide's {@link com.bumptech.glide.RequestBuilder#into(Target)} method returns the {@link
 * Target} instance you provided to make retaining a reference to the {@link Target} as easy as
 * possible. That said, you must wait until you're completely finished with the resource before
 * calling {@link com.bumptech.glide.RequestManager#clear(Target)} and you should always null out
 * references to any loaded resources in {@link Target#onLoadCleared(Drawable)}.
 *
 * <p>Always try to provide a size when using this class. Use {@link SimpleTarget#SimpleTarget(int,
 * int)} whenever possible with values that are <em>not</em> {@link Target#SIZE_ORIGINAL}. Using
 * {@link Target#SIZE_ORIGINAL} is unsafe if you're loading large images or are running your
 * application on older or memory constrained devices because it can cause Glide to load very large
 * images into memory. In some cases those images may throw {@link OutOfMemoryError} and in others
 * they may exceed the texture limit for the device, which will prevent them from being rendered.
 * Providing a valid size allows Glide to downsample large images, which can avoid issues with
 * texture size or memory limitations. You don't have to worry about providing a size in most cases
 * if you use {@link ViewTarget} so prefer {@link ViewTarget} over this class whenver possible.
 *
 * @see <a href="http://bumptech.github.io/glide/doc/targets.html">Glide's Target docs page</a>
 * @param <Z> The type of resource that this target will receive.
 * @deprecated Use {@link CustomViewTarget} if loading the content into a view, the download API if
 *     in the background
 *     (http://bumptech.github.io/glide/doc/getting-started.html#background-threads), or a {@link
 *     CustomTarget} for any specialized use-cases. Using {@link SimpleTarget} or {@link BaseTarget}
 *     is unsafe if the user does not implement {@link #onLoadCleared}, resulting in recycled
 *     bitmaps being referenced from the UI and hard to root-cause crashes.
 */
一个简单的Target基类,具有非基本方法的默认(通常为no-op)实现,允许调用者指定确切的宽度/高度。典型的用例看起来像这样:

目标目标=
Glide.with(片段)
.asBitmap ()
.load(“http://somefakeurl.com/fakeImage.jpeg”)
苹果(fitCenterTransform ())
.into(new SimpleTarget(250, 250)) {

@Override
public void onResourceReady(位图资源,过渡过渡){
//对bitmap做一些事情。});
}
//稍后,清除Target以释放资源,防止加载队列从
//吹出比例,并改善加载时间为未来的请求:
Glide.with(片段).clear(目标);


警告!这个类很容易被误用。使用SimpleTarget仅作为最后的手段。ViewTargetViewTarget的子类几乎总是一个更好的选择。

不要忘记清除这个类的实例!如果你必须使用这个类,请记住,与ViewTarget不同的是,如果每个实例都更新相同的底层视图或调用者,那么反复加载到这个类的新实例中是不安全的。如果你需要加载到同一个视图或调用者反复使用这个类,总是保留一个引用前面的实例和调用RequestManager.clear(目标)在旧实例之前,开始一个新的负载或你必须重用旧的实例的负载。GlideRequestBuilder.into(Target)方法返回您提供的目标实例,以便尽可能容易地保留对目标的引用。也就是说,你必须等到你完全完成资源调用RequestManager.clear(Target)之前,你应该总是空出对Target. onloadcleared (Drawable)中任何加载资源的引用。

在使用这个类时,始终尝试提供一个大小。只要可能,使用SimpleTarget(int, int)来处理不是Target.SIZE_ORIGINAL的值。使用目标。如果你正在加载大型图像,或者在旧的或内存受限的设备上运行应用程序,那么SIZE_ORIGINAL是不安全的,因为它会导致Glide将非常大的图像加载到内存中。在某些情况下,这些图像可能抛出OutOfMemoryError,在其他情况下,它们可能超过设备的纹理限制,这将阻止它们被渲染。提供一个有效的大小允许Glide采样较大的图像,这可以避免纹理大小或内存限制的问题。如果你使用ViewTarget,你不必担心在大多数情况下提供一个大小,所以尽可能使用ViewTarget而不是这个类。
弃用。
如果将内容加载到视图中,则使用CustomViewTarget;如果在后台(http://bumptech.github.io/glide/doc/getting-started.html#background-threads),则使用下载API;对于任何专门的用例,则使用CustomTarget。使用SimpleTargetBaseTarget是不安全的,如果用户没有实现basettarget . onloadcleared (android.graphics.drawable.Drawable),导致回收位图被从UI引用和难以根本原因崩溃。
@Deprecated
public abstract class SimpleTarget<Z> extends BaseTarget<Z> {
  private final int width;
  private final int height;

  /**
   * Constructor for the target that uses {@link Target#SIZE_ORIGINAL} as the target width and
   * height.
   */
  // Public API.
  @SuppressWarnings("WeakerAccess")
  public SimpleTarget() {
    this(SIZE_ORIGINAL, SIZE_ORIGINAL);
  }

  /**
   * Constructor for the target that takes the desired dimensions of the decoded and/or transformed
   * resource.
   *
   * @param width The width in pixels of the desired resource.
   * @param height The height in pixels of the desired resource.
   */
  // Public API.
  @SuppressWarnings("WeakerAccess")
  public SimpleTarget(int width, int height) {
    this.width = width;
    this.height = height;
  }

  /**
   * Immediately calls the given callback with the sizes given in the constructor.
   *
   * @param cb {@inheritDoc}
   */
  @Override
  public final void getSize(@NonNull SizeReadyCallback cb) {
    if (!Util.isValidDimensions(width, height)) {
      throw new IllegalArgumentException(
          "Width and height must both be > 0 or Target#SIZE_ORIGINAL, but given"
              + " width: "
              + width
              + " and height: "
              + height
              + ", either provide dimensions in the constructor"
              + " or call override()");
    }
    cb.onSizeReady(width, height);
  }

  @Override
  public void removeCallback(@NonNull SizeReadyCallback cb) {
    // Do nothing, we never retain a reference to the callback.
  }
}

TargetTracker

com.bumptech.glide.manager.TargetTracker
为RequestManager保存当前活动的目标集,并转发生命周期事件。


/**
 * Holds the set of {@link Target}s currently active for a {@link com.bumptech.glide.RequestManager}
 * and forwards on lifecycle events.
 */RequestManager保存当前活动的目标集,并转发生命周期事件。
public final class TargetTracker implements LifecycleListener {
  private final Set<Target<?>> targets =
      Collections.newSetFromMap(new WeakHashMap<Target<?>, Boolean>());

  public void track(@NonNull Target<?> target) {
    targets.add(target);
  }

  public void untrack(@NonNull Target<?> target) {
    targets.remove(target);
  }

  @Override
  public void onStart() {
    for (Target<?> target : Util.getSnapshot(targets)) {
      target.onStart();
    }
  }

  @Override
  public void onStop() {
    for (Target<?> target : Util.getSnapshot(targets)) {
      target.onStop();
    }
  }

  @Override
  public void onDestroy() {
    for (Target<?> target : Util.getSnapshot(targets)) {
      target.onDestroy();
    }
  }

  @NonNull
  public List<Target<?>> getAll() {
    return Util.getSnapshot(targets);
  }

  public void clear() {
    targets.clear();
  }
}

ThumbnailImageViewTarget

com.bumptech.glide.request.target.ThumbnailImageViewTarget
当加载多个固定尺寸的图像到ImageView时,避免额外调用View.requestLayout()。
通常,当使用RequestBuilder.thumbnail(com. bump技术。glide.requestbuilder) API加载多个图像到滚动列表的视图(如ListView, GridView,或RecyclerView)时,使用这个类是有意义的。

fixedsizerawable可能会导致倾斜或其他不良行为,这取决于你的图像、视图和缩放。如果出现这种情况,可以考虑DrawableImageViewTarget或BitmapImageViewTarget作为替代。


/**
 * Avoids extra calls to {@link android.view.View#requestLayout} when loading more than once image
 * into an {@link android.widget.ImageView} with fixed dimensions.
 *
 * <p>Typically it makes sense to use this class when loading multiple images with the {@link
 * com.bumptech.glide.RequestBuilder#thumbnail(com.bumptech.glide.RequestBuilder)} API into views in
 * a scrolling list like ListView, GridView, or RecyclerView.
 *
 * <p>{@link FixedSizeDrawable} may cause skewing or other undesirable behavior depending on your
 * images, views, and scaling. If this occurs, consider {@link DrawableImageViewTarget} or {@link
 * BitmapImageViewTarget} as alternatives.
 *
 * @param <T> The type of resource that will be displayed in the ImageView.
 */
 当加载多个固定尺寸的图像到ImageView时,避免额外调用View.requestLayout()。
通常,当使用RequestBuilder.thumbnail(com. bump技术。glide.requestbuilder) API加载多个图像到滚动列表的视图(ListView, GridView,或RecyclerView)时,使用这个类是有意义的。

fixedsizerawable可能会导致倾斜或其他不良行为,这取决于你的图像、视图和缩放。如果出现这种情况,可以考虑DrawableImageViewTargetBitmapImageViewTarget作为替代。
// Public API.
@SuppressWarnings("WeakerAccess")
public abstract class ThumbnailImageViewTarget<T> extends ImageViewTarget<T> {

  public ThumbnailImageViewTarget(ImageView view) {
    super(view);
  }

  /** @deprecated Use {@link #waitForLayout()} insetad. */
  @Deprecated
  @SuppressWarnings({"deprecation"})
  public ThumbnailImageViewTarget(ImageView view, boolean waitForLayout) {
    super(view, waitForLayout);
  }

  @Override
  protected void setResource(@Nullable T resource) {
    ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
    Drawable result = getDrawable(resource);
    if (layoutParams != null && layoutParams.width > 0 && layoutParams.height > 0) {
      result = new FixedSizeDrawable(result, layoutParams.width, layoutParams.height);
    }

    view.setImageDrawable(result);
  }

  protected abstract Drawable getDrawable(T resource);
}

小结

Glide的模板模式使用比较复杂,这个也是业务特性决定的,模板模式的行为结构继承层次越多越复杂;当看到复杂的模板模式的时候,先不要深入业务细节。先看看抽象类和接口的业务表达,然后根据接口和抽象类的业务表达进行梳理,这样才能找到主体业务线。类似RequestManager这样实现几个接口和业务线的类,在理解上先分开单独理解解析。之后再进行整体理解。

自研产品推荐

历时一年半多开发终于smartApi-v1.0.0版本在2023-09-15晚十点正式上线
smartApi是一款对标国外的postman的api调试开发工具,由于开发人力就作者一个所以人力有限,因此v1.0.0版本功能进行精简,大功能项有:

  • api参数填写
  • api请求响应数据展示
  • PDF形式的分享文档
  • Mock本地化解决方案
  • api列表数据本地化处理
  • 再加上UI方面的打磨

为了更好服务大家把之前的公众号和软件激活结合,如有疑问请大家反馈到公众号即可,下个版本30%以上的更新会来自公众号的反馈。
嗯!先解释不上服务端原因,API调试工具的绝大多数时候就是一个数据模型、数据处理、数据模型理解共识的问题解决工具,所以作者结合自己十多年开发使用的一些痛点来打造的,再加上服务端开发一般是面向企业的,作者目前没有精力和时间去打造企业服务。再加上没有资金投入所以服务端开发会滞后,至于什么时候会进行开发,这个要看募资情况和用户反馈综合考虑。虽然目前国内有些比较知名的api工具了,但作者使用后还是觉得和实际使用场景不符。如果有相关吐槽也可以在作者的公众号里反馈蛤!
下面是一段smartApi使用介绍:
在这里插入图片描述

下载地址:

https://pan.baidu.com/s/1kFAGbsFIk3dDR64NwM5y2A?pwd=csdn

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lichong951

你的鼓励决定更新的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值