IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 小白自我提高学习设计模式笔记(四)—责任链模式 -> 正文阅读

[移动开发]小白自我提高学习设计模式笔记(四)—责任链模式

前言

?????????结合着Android源码把所有的设计模式总结一下。

????????小白自我提高学习设计模式笔记(一)—代理模式

????????小白自我提高学习设计模式笔记(二)—装饰者模式

????????小白自我提高学习设计模式笔记(三)—装饰者模式在Android开发的小试

?????????小白自我提高学习设计模式笔记(四)—责任链模式


? ? ? ? 这几天在维护公司项目的时候,发现在APP启动的时候加载不同种类业务的逻辑有点混乱,所以计划通过责任链模式进行优化下相关代码。

一 责任链模式

1.定义

? ? ? ? 责任链(Chain of Responsibility)模式:也称为职责链模式。

????????主要就是解决一系列请求处理者。当发生请求的时候,客户端无需关心处理细节和请求的传递过程,请求会自动沿着“链”进行向下传递。

? ? ? ? 实现方式是通过当前对象持有其下一个对象的引用,而连成一条链。

? ? ? ? 是一种对象行为模式。

? ? ? ? 通常有三部分组成:

  • (1)Handler:抽象处理者。通常为抽象类或接口。定义处理请求的方法、处理具体处理者的规则、以及持有下一个Handler的引用;
  • (2)ConcreteHandler:具体的处理者。实现或继承Handler。根据具体处理者的处理规则对请求进行处理,如果不处理则转发给下一个Handler。负责对每个处理者进行处理或传递;
    • 这里要注意具体的一个处理者只能在两个行为中选择:要么处理Handler,要么将请求转发给下一个Handler
  • (3)Client:客户端。创建和使用责任链模式。不需要关心处理细节和请求的传递过程。

2.优缺点

(1)优点

  • 1)降低了对象之间的耦合度。每个对象之间无需了解知道处理细节和请求。
  • 2)增强了可扩展性,若新增一个对象,只需要新增一个具体的处理者即可;
  • 3)简化对象之间的连接,每个对象都会指向下一个对象,避免使用if...else;
  • 4)动态修改对象的次序以及新增或者删除不同的对象;
  • 5)单一职责。每个类只需处理单独的工作,不属于该类处理,自动传递给下一个对象。

(2)缺点

  • 1)需要Client来创建和维护这条链的合理性,增加了Client的复杂性,如果处理不当,容易造成循环调用
  • 2)不能保证所有的请求都能被处理

3.与装饰者模式的区别

  • (1)装饰者模式是动态增加对象的行为,持有被装饰对象,可以对其行为进行补充增强;而责任链模式是解决大量的对象需要调用,每个调用者都会持有下一个调用者对象,使用者是需要发起一次调用
  • (2)责任链模式是最终实现的效果为:对象1调用对象2,对象2调用对象3,对象3调用对象4......,每个具体的处理者就实现具体的处理内容和转发下一个处理者;而装饰者模式是(((对象1的功能)对象2的功能)对象3的功能.....),每个具体装饰类就实现具体的一个功能
  • (3)责任链模式是行为模式,即讲究的是对象之间的职责委派;而装饰者模式是结构型模式,即讲究的是将对象进行组装成更大的结构

二 应用场景

? ? ? ? 责任链模式通常会用到以下的场景:

  • ?(1)解决很长的if-else、switch-case结构,并且每个if中维护的复杂的处理事件;
  • (2)有多个对象都可以处理同一个请求,具体哪个对象处理需要在运行时自行确定;
  • (3)一个请求的处理需要多个对象才能完成;

? ? ? ? 那么这些类似的情况就使用责任链模式,可以对象封装到每个ConcreteHandler里面去处理具体的逻辑以及转发给下一个ConcreteHandler,通过Client将所有的ConcreteHandler组成链结构。

? ? ? ? 对于经典的责任链模式是多个请求处理者形成一条链结构在处理请求的时候,其中只有一个请求处理者会真正的进行处理该请求,并会立即停止传递。其实在实际应用中,还会对其进行扩展,像后面介绍的OkHttp的责任链模式,就是每个请求处理者都会进行处理请求,并返回相应的结果。后面会基于这种思想,对APP启动时的一些业务场景进行优化。

1.Android源码中的责任链模式

? ? ? ? Android中的事件分发机制就是一个经典的责任链模式。当产生一个touch事件的时候,Activity的dispatchTouchEvent()会将touch事件分发到ViewGroup上,如果该ViewGroup需要处理该touch事件,可通过onInterceptTouchEvent()来拦截该touch事件,然后交给当前ViewGroup的OnTouchEvent()来处理;如果该ViewGroup不需要处理该touch事件,则可向下传递,直到有View来接受或放弃该touch事件,如果View放弃该touch事件,则事件依次往上返回到Activity,交由Activity来接受或放弃该touch事件。

?????? ?该touch事件就可以看作一个需要处理的请求,而ViewGroup、View就可以看成ConcreteHandler,该touch事件会在不同的ConcreteHandler里进行传递,直到有一个ConcreteHandler处理了该事件,则中止touch事件的传递。所以这里就是一个经典的责任链模式的设计思想。

???因为涉及的内容比较多,所以单独去总结一篇Android的事件分发机制(遗留问题:补充链接

2.OkHttp源码中的责任链模式

? ? ? ? 在OkHttp请求接口的时候,大体代码如下:

    public void btnOkHttp(View view) {
        Request request = new Request.Builder()
                .url("接口地址")
                .get()
                .build();
        OkHttpClient client = new OkHttpClient();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
            }
        });
    }

?????????基于OkHttp3来看下这个责任链模式的运用(OkHttp的详细处理过程不做太多列举,仅看下责任链模式是怎么运用)。在client.newCall()返回的是RealCall,通过调用的RealCall中的?enqueue(),最终调用到AsyncCall的execute()方法,AsyncCall的execute()方法的逻辑大体如下:

 @Override protected void execute() {
      boolean signalledCallback = false;
      timeout.enter();
      try {
        Response response = getResponseWithInterceptorChain();
........

(1)Client??

????????进入到?getResponseWithInterceptorChain()为责任链模式的Client,在这里将责任链的每个ConcreteHandler组成链结构,并启动该责任链,代码如下:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //将interceptors传入到每个具体的ConcreteHandler,组成链结构
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    //启动责任链
    return chain.proceed(originalRequest);
  }

?????????这里将一些具有功能的拦截器interceptors集合中,作为参数传入到RealInterceptorChain,然后实例化链节点RealInterceptorChain,通过调用chain.proceed()启动链。

????????proceed()的传入参数为request,那么可以猜想到每个拦截器interceptor就是对在传入的request进行增加相应的功能,并且依次将request向下传递给下一个RealInterceptorChain节点,最后将每个RealInterceptorChain节点中的拦截器处理的response返回。

(2)抽象的Handler

????????Interceptor.Chain为该责任链模式的抽象Handler,其中proceed()为整个责任链的执行流程的方法,需要ConcreteHandler实现proceed()方法,由于传入proceed()的为Request,所以整个责任链就是对request的处理。

(3)ConcreteHandler

? ????????RealInterceptorChain为?ConcreteHandler。该类的作用就是对传入的request经过每个拦截器进行处理后提交给服务器,最后服务器处理完之后将response依次向上返回,代码如下:

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
........
    // 初始化下一个节点,此时传入的索引值为下一个拦截器的索引值
     RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    //从拦截器集合中取出当前索引值下的拦截器
     Interceptor interceptor = interceptors.get(index);
    //执行该拦截器
     Response response = interceptor.intercept(next);
.......
    return response;
}

? ? ? ? 从??(1)Client?? 中启动链结构的时候,初始化第一个节点的时候,传入的所有拦截器集合interceptors和索引值index为0,所以ConcreteHandler就会完成三件事情:

  • 1)根据传入的下一个节点的索引值和拦截器集合interceptors实例化出下一个链结构的节点next;
  • 2)从拦截器集合中取出当前的拦截器;
  • 3)通过调用?interceptor.intercept()执行拦截器的拦截操作,并且将下一个节点作为参数传入到该拦截器中;

? ? ? ? 在第3)步中,举例BridgeInterceptor来看下interceptor是怎么进行拦截操作,然后怎么转交给下一个节点。

? ? ? ? 当调用到BridgeInterceptor的intercept()方法的时候,主要就是完成三部分内容:

  • 1)前半部分就是将用户设置的request转换成网络请求所需要的request,例如添加header等;
  • 2)然后通过调用传进来的下一个节点chain.proceed()将处理完的request传递给下一个链结构节点;
  • 3)最后将该节点返回的response进行在处理,然后向上返回response。

????????代码如下:

public final class BridgeInterceptor implements Interceptor {
     @Override 
     public Response intercept(Chain chain) throws IOException {
    //...... 处理request
    //向下传递
       Response networkResponse = chain.proceed(requestBuilder.build());    
    // ......处理response,返回response  

? ? ? 同样CacheInterceptor、ConnectInterceptor等也是如此通过chain.proceed()将request传递给下一个节点,一直到最后一个CallServerInterceptor拦截器就不在调用下一个节点,当CallServerInterceptor拦截器执行完intercept()方法之后,不在向下转发,而是将处理完的结果依次向上返回。

(4)小结

? ? ? ? 所以从整个源码流程来看,对于OkHttp的责任链模式:

  • 1)ConcreteHandler是RealInterceptorChain类,负责实例化下一个链节点、处理当前节点内容以及转发下一个链节点?。但?RealInterceptorChain将具体实现?交给了每个拦截器Interceptor;所以Interceptor是一种模板模式的体现;
  • 2)区别于经典的责任链模式,并不是仅将请求处理者交给一个ConcreteHandler进行处理,而是每个ConcreteHandler都会处理请求,并且都有返回;
  • 3)在AsyncCall的?getResponseWithInterceptorChain()为责任链模式的Client,负责创建责任链以及启动责任链

? ? ? ? 另外从RealInterceptorChain的源码中也发现,如果我们在自定义一个Interceptor的时候,在复写intercept()的时,一定要调用chain.proceed()将该链结构串起来,否则框架中的后面的几个拦截器根本根本不会执行。

3.对APP启动逻辑的优化

????????在一个APP启动的时候很有可能会涉及到比较多的业务场景:例如隐私权限提示、各种广告展示(如全屏广告、半屏广告、对话框形式等)、提示用户升级版本等等,而这些UI在显示的过程中希望能够按照一定的顺序执行完,并且能够每次只显示一种。那么就有了下面的逻辑处理大致如下:

? ? ? ? (1)根据不同的标示来确定是否显示对应的不同的业务场景,代码大体如下:

????????

? ? ? ? (2)?而每个在处理每个业务场景的时候,又会有一些逻辑处理条件:像如果上一个广告是否在显示的时候,该广告就不显示;像如果不符合该广告显示的条件的时候,在重新调用(1)中的方法来显示其他的业务逻辑……

????????

? ? ? ? 这只是列举了一个业务场景的代码,当APP启动的时候,会有多个业务场景,那么代码就会大量堆积,代码逻辑非常混乱,若要调整UI显示顺序不容易维护,?那么其实这种情况完全可以使用责任链模式来优化上面的逻辑。

(1)思路整理

? ? ? ? 首先这些业务场景都有一个共同的特点:

  • ? ? ? ? 1)根据是否符合业务场景的条件,如果符合该业务场景,则交给该业务进行处理;如果不符合该业务场景,则交给下一个业务进行处理
  • ? ? ? ?2) 还有大量的if-else逻辑来决定执行哪个业务场景

? ? ? ? 那么就有了在该责任链模式的大体架构:????????

  • ? ? ? ? 1)抽象的Handler:持有Handler对象+一个处理业务逻辑的抽象方法+设置下一个Handler的方法
  • ? ? ? ? 2)每个具体的ConcreteHandler:处理当前业务场景的逻辑+跳转到下一个Handler+控制跳转的逻辑
  • ? ? ? ? 3)Client:将这些业务场景串起来,对外提供使用

(2)代码

  • ? ?1)抽象Handler类AppLauncherEventHandler

? ? ? ? 相比较于责任链模式的抽象Handler增加一个isSelfAppLauncherEventHandler()方法来在每个具体的ConcreteHandler中实现跳转具体的业务逻辑对应的条件,代码如下:

public abstract class AppLauncherEventHandler {

    /**
     * 设置的是下一个Handler
     */
    private AppLauncherEventHandler nextEventHandler;

    /**
     * 获取下一个Handler
     *
     * @return
     */
    protected AppLauncherEventHandler getNextAppLauncherEventHandler() {
        return this.nextEventHandler;
    }

    /**
     * 设置其下一个Handler
     *
     * @param handler
     */
    protected void setNextAppLauncherEventHandler(AppLauncherEventHandler handler) {
        this.nextEventHandler = handler;
    }

    /**
     * 如果设置了下一个Handler,则调用下一个Handler
     */
    protected void goToNextAppLauncherEventHandler(Activity context) {
        if (getNextAppLauncherEventHandler() == null) {
            return;
        }
        getNextAppLauncherEventHandler().handlerAppLauncherEvent(context);
    }

    /**
     * 用来处理该子类需要实现的功能
     *
     * @param context
     */
    protected abstract void handlerAppLauncherEvent(Activity context);

    /**
     * 当前Handler需要处理
     *
     * @return true:则直接交给当前Handler;
     * false:交给下一个Handler进行处理
     */
    protected abstract boolean isSelfAppLauncherEventHandler(Activity context);
}
  • 2)增加一个具体ConcreteHandler的父类AppLauncherToNextEventHandler

? ? ? ? 由于所有的ConcreteHandler都需要根据条件来决定时本ConcreteHandler来处理还是交给下一个ConcreteHandler来处理,所以通过模板设计方式(遗留问题:增加链接)增加一个所有的ConcreteHandler的父类,继承AppLauncherEventHandler,来将所有的ConcreteHandler进行模板化,代码如下:

public abstract class AppLauncherToNextEventHandler extends AppLauncherEventHandler {

    @Override
    protected void handlerAppLauncherEvent(Activity context) {
        //符合该条件的直接交给该Handler进行处理
        if (isSelfAppLauncherEventHandler(context)) {
            Log.v(String.format("~~~~~~~~~~~~ 进入到 \"%s\" 处理逻辑", getClass().getSimpleName()));
            handlerSelfAppLauncherEvent(context);
            return;
        }
        Log.d(String.format("~~~~~~~~~~~~ 交给下一个 \"%s\" 处理逻辑", getClass().getSimpleName()));
        //否则交给下一个Handler进行处理
        goToNextAppLauncherEventHandler(context);
    }

    /**
     * 显示本Handler处理的广告逻辑
     */
    protected abstract void handlerSelfAppLauncherEvent(Activity context);

}

????????那么在每个具体Handler中只需要通过isSelfAppLauncherEventHandler()来实现每个业务跳转的逻辑和handlerSelfAppLauncherEvent()来处理本ConcreteHandler处理的逻辑即可。

  • 3)举例一个具体的ConcreteHandler类FullScreenTypeAdvertEventHandler

? ? ? ? 使用对话框来模拟一下具体业务场景下UI的显示,可以看到该ConcreteHandler类中只需要实现具体的UI显示,并且通过在isSelfAppLauncherEventHandler()中增加什么条件下交给该ConcreteHandler类进行处理即可,同时通过在处理完本ConcreteHandler的具体逻辑后,还可以根据业务场景条件跳转到下一个Handler,代码如下:

public class FullScreenTypeAdvertEventHandler extends AppLauncherToNextEventHandler {

    @Override
    protected void handlerSelfAppLauncherEvent(Activity context) {
        PatternApplication.getInstance().isShowedFullScreenAdvert = true;
        FakeBusinessDialogUtils.showFakeBusinessDialog(context, "FullScreenAdvert", "正在显示一个全屏广告", new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialogInterface) {
                //当处理完该业务之后,也需要跳转到下一个Handler
                goToNextAppLauncherEventHandler(context);
            }
        });
    }
    //不符合该条件,已有父类跳转到下一个Handler
    @Override
    protected boolean isSelfAppLauncherEventHandler(Activity context) {
        //最基本前提:从来没有显示过 && 获取的广告内容有效(该逻辑不做展示)
        return !PatternApplication.getInstance().isShowedFullScreenAdvert;
    }
}

? ? ? ? ?其他的业务场景对应的ConcreteHandler,不在一一列举,代码已经上传到github:地址为https://github.com/wenjing-bonnie/pattern.git的com.android.pattern.chain下的相关代码。

  • 4)通过AppLauncherChain来组成责任链,对外提供启动链的方法

? ? ? ? 实例化每个具体的ConcreteHandler,然后根据每个业务场景的顺序来设置责任链,最后通过调用第一个ConcreteHandler的handlerAppLauncherEvent()来启动该责任链,代码如下:

public class AppLauncherChain {
    private PrivateConfirmEventHandler privateConfirm;
    private CheckNotificationEventHandler check;
    private DownloadNewVersionEventHandler download;
    //广告
    private FullScreenTypeAdvertEventHandler fullScreen;
    private CloseTypeAdvertEventHandler close;
    private AlertMessageAdvertEventHandler alertMessage;

    public AppLauncherChain() {
        initAppLauncherChain();
        setAppLauncherChain();
    }

    /**
     * 实例化每个Handler
     */
    private void initAppLauncherChain() {
        privateConfirm = new PrivateConfirmEventHandler();
        check = new CheckNotificationEventHandler();
        fullScreen = new FullScreenTypeAdvertEventHandler();
        close = new CloseTypeAdvertEventHandler();
        alertMessage = new AlertMessageAdvertEventHandler();
        download = new DownloadNewVersionEventHandler();
    }

    /**
     * 设置责任链结构
     */
    private void setAppLauncherChain() {
        //设置责任链
        privateConfirm.setNextAppLauncherEventHandler(check);
        check.setNextAppLauncherEventHandler(fullScreen);
        fullScreen.setNextAppLauncherEventHandler(close);
        close.setNextAppLauncherEventHandler(alertMessage);
        alertMessage.setNextAppLauncherEventHandler(download);
    }

    /**
     * APP启动的时候的责任链
     *
     * @param context
     */
    public void startAppLauncherChain(Activity context) {
        //启动责任链
        privateConfirm.handlerAppLauncherEvent(context);
    }

}

? ? ? ? 运行项目,通过AppLauncherChainActivity就可以看到具体的效果。?代码已经上传到github:地址为https://github.com/wenjing-bonnie/pattern.git的com.android.pattern.chain下的相关代码。?

(3)小结

? ? ? ? 通过责任链模式,成功的将以前复杂难以梳理逻辑的APP启动时的业务逻辑代码已经优化。相比较之前代码:

  • 1)不仅降低了对象之间的耦合度:每个业务场景单独逻辑判断,不在需要依赖于其他业务场景是否在显示来决定本业务UI是否显示;
  • 2)增强了可扩展性:若新增一个业务场景,只需要继承AppLauncherToNextEventHandler,然后加到AppLauncherChain的链结构即可;
  • 3)可以通过调整AppLauncherChain的链结构来比较方便的动态调整UI的显示顺序;
  • 4)每个具体的ConcreteHandler单独处理业务逻辑,代码逻辑简单;
  • 5)通过AppLauncherChain的链结构避免了繁琐的if-else的结构

三 总结

? ? ? ? 通过一些源码以及在自己通过业务场景对责任链模式的实践,现在已经掌握了责任链模式:

  • 1.责任链模式就是解决一系列的请求处理者。当发生请求的时候,客户端无需关心细节和请求的传递,该请求会自动沿链传递,直到有请求处理者处理该请求;
  • 2.在每个请求处理者中都持有下一对象的引用,用来向下传递请求;
  • 3.每个责任链模式包括三部分的内容:
    • (1)抽象Handler:一般为抽象类或接口。负责定义请求的方法以及持有下一个Handler;
    • (2)ConcreteHandler:具体的Handler。负责实现具体的请求处理逻辑以及跳转到下一个Handler;
    • (3)Client:客户端。负责创建和启动责任链模式。
  • 4.经典的责任链模式就是有且仅有一个请求处理者来处理请求,只要有请求处理者处理请求,就会中止链的传递;
  • 5.相对于经典的责任链模式,还有一种就是对责任链模式的扩展:每个ConcreteHandler都可以对请求进行处理,在ConcreteHandler中根据不同的条件跳转到下一个Handler,例如OkHttp;
  • 6.责任链模式可以方便的解决if-else以及swich-case结构或者一个请求需要多个请求处理者对该请求进行处理;
  • 7.责任链模式扩展性强,可以通过增加请求处理者的方式,就可以对请求进行处理;
  • 8.责任链模式可以动态的修改请求处理者的顺序、删除以及新增;
  • 9.责任链模式中的每个请求处理者只负责一种功能,不符合的自动转发给下一个Handler,具有单一职责;
  • 10.责任链模式需要Client自行维护链结构,如果处理不当,容易造成循环调用;
  • 11.责任链模式可以配合模板模式将ConcreteHandler模板化,提高代码的复用率;
  • 12.Android的事件分发机制就是一种经典的责任链模式;当产生一个touch事件的时候,会一直传递,直到有一个ConcreteHandler(ViewGroup或View)来处理该事件,则中止事件的传递;
  • 13.OkHttp源码中的ConcreteHandler为RealInterceptorChain,负责对请求进行处理以及转发给下一个Handler,只不过RealInterceptorChain将这部分工作转交给了每个Interceptor;如果把每个Interceptor看作通过模板模式对ConcreteHandler的模板化,似乎更容易理解在OkHttp中的责任链模式。

? ? ? ? 越来越多的东西需要去学习和总结,加油!?

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-09 11:53:22  更:2021-09-09 11:55:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 17:06:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码