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进阶笔记-3. Service 启动过程 & 绑定过程 -> 正文阅读

[移动开发]Android进阶笔记-3. Service 启动过程 & 绑定过程

startService和bindService

  • Service的启动方式主要有两种,分别是startService和bindService
  • 使用startService启动时是单独开一个服务,与Activity没有任何关系,而bindService方式启动时,Service会和Activity进行绑定,当对应的activity销毁时,对应的Service也会销毁
  • startService多次,onStart()会执行多次,onCreate()只执行一次,onStartCommand()也会执行多次。bindService多次,onCreate()与onBind()都只会调用一次。
  • Service中的onBind()方法是抽象方法,Service类本身就是抽象类,所以onBind()方法是必须重写的,即使我们用不到。
  • bindService启动的服务和调用者之间是典型的Client-Server模式。调用者是client,Service则是Server
    端。Service只有一个,但绑定到Service上面的Client可以有一个或很多个。bindService启动服务的生
    命周期与其绑定的client息息相关。
  • 调用Context.startService方式启动Service时,如果Android面临内存匮乏,可能会销毁当前运行的
    Service,待内存充足时可以重建Service。而Service被Android系统强制销毁并再次重建的行为依赖于Service的onStartCommand()方法的返回值
    1. START_STICKY: 如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由 于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。 如果在此期间没有任何启动命令被传 递到service,那么参数Intent将为null。
    2. START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务
    3. START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
    4. START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

Service的启动过程

ContextWrapper.startService() & ContextImpl.startService()
  • 启动Service是通过调用startService实现的,其方法实现在Context的实现类ContextWrapper中
Context mBase;

@Override
public ComponentName startService(Intent service) {
    return mBase.startService(service);
}
  • Context mBase 指的是什么呢?ActivityThread启动Activity时会调用performLaunchActivity创建Activity的上下文环境
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    //创建上下文对象appContext
    ContextImpl appContext = createBaseContextForActivity(r);
    ...
    try {
        ...
        if (activity != null) {
            ...
            //将appContext传入Activity的attach方法中,将Activity与appContext关联
            activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor, window, r.configCallback,
                    r.assistToken);
            ...
        }
        ...
    } catch ...
    return activity;
}

//其中createBaseContextForActivity实现如下
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
    ...
    ContextImpl appContext = ContextImpl.createActivityContext(
            this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
    ...
    return appContext;
}
  • 从上面代码可以看出,Activity的attach方法中将ContextImpl赋值给ContextWrapper的成员变量mBase,因此mBase具体指向就是ContextImpl;
  • 那么来看一下ContextImpl的startService方法
@Override
public ComponentName startService(Intent service) {
    warnIfCallingFromSystemProcess();
    return startServiceCommon(service, false, mUser);
}

//其中调用了startServiceCommon代码如下
private ComponentName startServiceCommon(Intent service, boolean requireForeground, UserHandle user) {
    try {
        ...
        //
        ComponentName cn = ActivityManager.getService().startService(
                mMainThread.getApplicationThread(), service,
                service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
                getOpPackageName(), getAttributionTag(), user.getIdentifier());
        ...
        return cn;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
  • 上面代码调用ActivityManager.getService()获取到ActivityManagerService,并调用其startService方法
AMS.startService()
  • ActivityManagerService.startService代码如下
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
        String resolvedType, boolean requireForeground, String callingPackage,
        String callingFeatureId, int userId) throws TransactionTooLargeException {
    ...
    synchronized(this) {
        final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        ComponentName res;
        try {
            res = mServices.startServiceLocked(caller, service,
                    resolvedType, callingPid, callingUid,
                    requireForeground, callingPackage, callingFeatureId, userId);
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
        return res;
    }
}
ActiveServices.startServiceLocked
  • 上面调用了mServices.startServiceLocked方法,其中mServices的类型是ActiveServices
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
            int callingPid, int callingUid, boolean fgRequired, String callingPackage,
            @Nullable String callingFeatureId, final int userId,
            boolean allowBackgroundActivityStarts) throws TransactionTooLargeException {
    ...
    ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
    ...
    return cmp;
}
//startServiceLocked其中又调用了startServiceInnerLocked
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
        boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
    ...
    String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
    ...
    return r.name;
}
//startServiceInnerLocked中又调用了bringUpServiceLocked
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
        boolean whileRestarting, boolean permissionsReviewRequired) throws TransactionTooLargeException {
    ...
    //ServiceRecord的processName的值赋值给procName, 用来描述Service想要在哪个进程运行,默认是当前进程,
    //我们也可以在AndroidManifes配置文件中设置android:process属性来新开启一个进程运行Service
    final String procName = r.processName;
    ProcessRecord app;
    if (!isolated) {
        //将procName和Service的uid传入到AMS的getProcessRecordLocked方法,
        //来查询是否存在一个与Service对应的ProcessRecord类型的对象app,
        //ProcessRecord主要用来记录运行的应用程序进程的信息.
        app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);

        //如果用来运行Service的应用程序进程存在,则调用realStartServiceLocked方法
        if (app != null && app.thread != null) {
            try {
                app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode, mAm.mProcessStats);
                realStartServiceLocked(r, app, execInFg);
                return null;
            } catch ...
        }
    } else {
        app = r.isolatedProc;
        ...
    }

    //如果用来运行Service的应用程序进程不存在,或应用程序之间的组件调用不需要检查权限,
    //满足则调用AMS的startProcessLocked方法来创建对应的应用程序进程。
    if (app == null && !permissionsReviewRequired) {
        if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated, false)) == null) {
            String msg = "Unable to launch app "
                    + r.appInfo.packageName + "/"
                    + r.appInfo.uid + " for service "
                    + r.intent.getIntent() + ": process is bad";
            bringDownServiceLocked(r);
            return msg;
        }
        ...
    }
    ...
    return null;
}

//realStartServiceLocked方法代码如下
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
    ...
    try {
        ...
        //app.thread的实现是ActivityThread的内部类ApplicationThread
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
                app.getReportedProcState());
        r.postNotification();
        created = true;
    }
    ...
}
ActivityThread.ApplicationThread.scheduleCreateService()
  • scheduleCreateService代码如下
public final void scheduleCreateService(IBinder token,
        ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
    updateProcessState(processState, false);
    //将要启动的信息封装成CreateServiceData 对象并传给sendMessage方法
    CreateServiceData s = new CreateServiceData();
    s.token = token;
    s.info = info;
    s.compatInfo = compatInfo;
    //sendMessage向H发送CREATE_SERVICE消息
    sendMessage(H.CREATE_SERVICE, s);
}
ActivityThread.sendMessage() & ActivityThread.H.handleMessage()
  • scheduleCreateServiceActivity调用sendMessage向H发送CREATE_SERVICE消息,Thread.H.handleMessage根据消息类型,调用handleCreateService方法
class H extends Handler {
    ...
    public void handleMessage(Message msg) {
        switch (msg.what) {
            ...
             case CREATE_SERVICE:
                  ...
                  //根据消息类型,调用handleCreateService方法
                  handleCreateService((CreateServiceData)msg.obj);
                  ...
                  break;
            ...
        }
        ...
    }
}
ActivityThread.handleCreateService
  • ActivityThread.H.handleMessage方法根据消息类型,调用了handleCreateService方法
private void handleCreateService(CreateServiceData data) {
    ...
    //获取要启动Service的应用程序的LoadedApk,LoadedApk是一个APK文件的描述类
    LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        ...
        //创建Service的上下文环境ContextImpl对象
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        //通过调用LoadedApk的getClassLoader方法来获取类加载器
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        //根据CreateServiceData对象中存储的Service信息,将Service加载到内存中
        service = packageInfo.getAppFactory().instantiateService(cl, data.info.name, data.intent);
        ...
        //调用attach方法来初始化Service
        service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService());
        //调用Service的onCreate方法,这样Service就启动
        service.onCreate();
        //将启动的Service加入到ActivityThread的成员变量mServices中,其中mServices是ArrayMap类型
        mServices.put(data.token, service);
        ...
    } catch...
}
  • 最终在ActivityThread.handleCreateService中创建service,并调用 attach 和 onCreate 方法

Service的绑定过程

ContextWrapper.bindService() & ContextImpl.bindService
  • 绑定Service是通过调用bindService()实现的,其方法实现在Context的实现类ContextWrapper中
@Override
public boolean bindService(Intent service, ServiceConnection conn,
        int flags) {
    return mBase.bindService(service, conn, flags);
}
  • 之前已经说过mBase具体指向就是ContextImpl,那么来看一下ContextImpl.bindService
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
    warnIfCallingFromSystemProcess();
    return bindServiceCommon(service, conn, flags, null, mMainThread.getHandler(), null,
            getUser());
}
  • bindService中调用了bindServiceCommon,代码如下
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
        String instanceName, Handler handler, Executor executor, UserHandle user) {
    IServiceConnection sd;
    ...
    //调用了LoadedApk类型的对象mPackageInfo的getServiceDispatcher方法,
    //主要作用是将ServiceConnection封装为IServiceConnection类型的对象sd,
    //从IServiceConnection的名字就能得知它实现了Binder机制,这样Service的绑定就支持了跨进程
    if (mPackageInfo != null) {
        if (executor != null) {
            sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), executor, flags);
        } else {
            sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags);
        }
    } else {
        throw new RuntimeException("Not supported in system context");
    }
    validateServiceIntent(service);
    try {
        IBinder token = getActivityToken();
        ...
        //调用AMS的bindService方法
        int res = ActivityManager.getService().bindIsolatedService(
            mMainThread.getApplicationThread(), getActivityToken(), service,
            service.resolveTypeIfNeeded(getContentResolver()),
            sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
        ...
        return res != 0;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
AMS.bindIsolatedService()
public int bindIsolatedService(IApplicationThread caller, IBinder token, Intent service,
        String resolvedType, IServiceConnection connection, int flags, String instanceName,
        String callingPackage, int userId) throws TransactionTooLargeException {
    ...
    synchronized(this) {
        //调用ActiveServices类型的对象mServices的bindServiceLocked方法
        return mServices.bindServiceLocked(caller, token, service,
                resolvedType, connection, flags, instanceName, callingPackage, userId);
    }
}
ActiveServices.bindServiceLocked()
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
        String resolvedType, final IServiceConnection connection, int flags,
        String instanceName, String callingPackage, final int userId) throws TransactionTooLargeException {
    ...
    try {
        ...
        if ((flags&Context.BIND_AUTO_CREATE) != 0) {
            s.lastActivity = SystemClock.uptimeMillis();
            //调用bringUpServiceLocked方法,在bringUpServiceLocked方法中又会调用realStartServiceLocked方法,
            //最终由ActivityThread来调用Service的onCreate方法启动Service,
            if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
                    permissionsReviewRequired) != null) {
                return 0;
            }
        }
        ...
        //s.app != null 表示Service已经运行,其中s是ServiceRecord类型对象,app是ProcessRecord类型对象
        //b.intent.received表示当前应用程序进程的Client端已经接收到绑定Service时返回的Binder,
        //这样应用程序进程的Client端就可以通过Binder来获取要绑定的Service的访问接口
        if (s.app != null && b.intent.received) {
            try {
                //调用c.conn的connected方法,其中c.conn指的是IServiceConnection,
                //它的具体实现为ServiceDispatcher.InnerConnection,其中ServiceDispatcher是LoadedApk的内部类,
                //InnerConnection的connected方法内部会调用H的post方法向主线程发送消息,从而解决当前应用程序进程和Service跨进程通信的问题,
                c.conn.connected(s.name, b.intent.binder, false);
            } catch ...
            if (b.intent.apps.size() == 1 && b.intent.doRebind) {
                //如果当前应用程序进程的Client端第一次与Service进行绑定的,并且Service已经调用过onUnBind方法,
                requestServiceBindingLocked(s, b.intent, callerFg, true);
            }
        } else if (!b.intent.requested) {
            //如果应用程序进程的Client端没有发送过绑定Service的请求,rebind为false,表示不是重新绑定
            requestServiceBindingLocked(s, b.intent, callerFg, false);
        }
        ...
    } finally {
        Binder.restoreCallingIdentity(origId);
    }
    return 1;
}
  • 上面最终调用了ActiveServices.requestServiceBindingLocked代码如下
private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
        boolean execInFg, boolean rebind) throws TransactionTooLargeException {
    ...
    //i.requested表示是否发送过绑定Service的请求
    if ((!i.requested || rebind) && i.apps.size() > 0) {
        try {
            ...
            r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
                    r.app.getReportedProcState());
            ...
        } catch ...
    }
    return true;
}
  • 上面代码requestServiceBindingLocked中调用了ActivityThread.ApplicationThread.scheduleBindService
ActivityThread.ApplicationThread.scheduleBindService()
public final void scheduleBindService(IBinder token, Intent intent,
        boolean rebind, int processState) {
    updateProcessState(processState, false);
    BindServiceData s = new BindServiceData();
    s.token = token;
    s.intent = intent;
    s.rebind = rebind;
    //向H发送BIND_SERVICE消息
    sendMessage(H.BIND_SERVICE, s);
}
ActivityThread.sendMessage() & ActivityThread.H.handleMessage()
class H extends Handler {
    ...
    public void handleMessage(Message msg) {
        switch (msg.what) {
            ...
             case BIND_SERVICE:
                  ...
                  //根据消息类型,调用handleBindService方法
                  handleBindService((BindServiceData)msg.obj);
                  ...
                  break;
            ...
        }
        ...
    }
}
ActivityThread.handleBindService()
private void handleBindService(BindServiceData data) {
    //从mServices中获取要绑定的Service
    Service s = mServices.get(data.token);
    if (s != null) {
        //如果取到了
        try {
            data.intent.setExtrasClassLoader(s.getClassLoader());
            data.intent.prepareToEnterProcess();
            try {
                //判断是否rebind
                if (!data.rebind) {
                    //调用Service的onBind方法,这样Service处于绑定状态了
                    IBinder binder = s.onBind(data.intent);
                    //调用AMS的publishService方法
                    ActivityManager.getService().publishService(
                            data.token, data.intent, binder);
                } else {
                    s.onRebind(data.intent);
                    ActivityManager.getService().serviceDoneExecuting(
                            data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
                }
            } catch ...
        } catch ...
    }
}
  • handleBindService中调用了Service的onBind方法,又调用了AMS的publishService方法
AMS.publishService()
public void publishService(IBinder token, Intent intent, IBinder service) {
    ...
    synchronized(this) {
        ...
        //调用了ActiveServices类型的mServices对象的publishServiceLocked方法
        mServices.publishServiceLocked((ServiceRecord)token, intent, service);
    }
}
ActiveServices.publishServiceLocked()
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
    final long origId = Binder.clearCallingIdentity();
    try {
        ...
        if (r != null) {
            ...
            if (b != null && !b.received) {
                ...
                for (int conni = connections.size() - 1; conni >= 0; conni--) {
                    ...
                    for (int i=0; i<clist.size(); i++) {
                        ...
                        try {
                            //c.conn指的是IServiceConnection,它的具体实现为ServiceDispatcher.InnerConnection,
                            //其中ServiceDispatcher是LoadedApk的内部类
                            c.conn.connected(r.name, service, false);
                        } catch...
                    }
                }
            }
            ...
        }
    } finally {
        Binder.restoreCallingIdentity(origId);
    }
}
  • publishServiceLocked中调用了LoadedApk.ServiceDispatcher.InnerConnection.connected()
LoadedApk.ServiceDispatcher.InnerConnection.connected()
public void connected(ComponentName name, IBinder service, boolean dead)
        throws RemoteException {
    LoadedApk.ServiceDispatcher sd = mDispatcher.get();
    if (sd != null) {
        sd.connected(name, service, dead);
    }
}
  • 上面代码调用了ServiceDispatcher 类型的sd对象的connected方法
LoadedApk.ServiceDispatcher.connected()
public void connected(ComponentName name, IBinder service, boolean dead) {
    if (mActivityExecutor != null) {
        mActivityExecutor.execute(new RunConnection(name, service, 0, dead));
    } else if (mActivityThread != null) {
        //调用Handler类型的对象mActivityThread的post方法,mActivityThread实际上指向的是H。
        //因此,通过调用H的post方法将RunConnection对象的内容运行在主线程中
        mActivityThread.post(new RunConnection(name, service, 0, dead));
    } else {
        doConnected(name, service, dead);
    }
}
  • connected最终调用了doConnected方法
LoadedApk.ServiceDispatcher.doConnected()
public void doConnected(ComponentName name, IBinder service, boolean dead) {
    ...
    if (old != null) {
        mConnection.onServiceDisconnected(name);
    }
    if (dead) {
        mConnection.onBindingDied(name);
    }
    if (service != null) {
        //调用了ServiceConnection类型的对象mConnection的onServiceConnected方法,
        //这样在客户端中实现了ServiceConnection接口的类的onServiceConnected方法就会被执行
        mConnection.onServiceConnected(name, service);
    } else {
        mConnection.onNullBinding(name);
    }
}
为什么bindService可以跟Activity生命周期联动?
  1. bindService 方法执行时,LoadedApk 会记录 ServiceConnection 信息。
  2. Activity 执行 finish 方法时,会通过 LoadedApk 检查 Activity 是否存在未注销/解绑的 BroadcastReceiver和 ServiceConnection,如果有,那么会通知 AMS 注销/解绑对应的 BroadcastReceiver 和 Service,并打印异常信息,告诉用户应该主动执行注销/解绑的操作。
如何保证Service不被杀死?
  • Android 进程不死从3个层面入手:
1. 提供进程优先级,降低进程被杀死的概率
  • 方法一:监控手机锁屏解锁事件,在屏幕锁屏时启动1个像素的 Activity,在用户解锁时将 Activity 销毁掉。
  • 方法二:启动前台service。
  • 方法三:提升service优先级:在AndroidManifest.xml文件中对于intent-filter可以通过android:priority = "1000"这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播。
2. 在进程被杀死后,进行拉活
  • 方法一:注册高频率广播接收器,唤起进程。如网络变化,解锁屏幕,开机等
  • 方法二:双进程相互唤起。
  • 方法三:依靠系统唤起。
  • 方法四:onDestroy方法里重启service:service + broadcast 方式,就是当service走ondestory的时候,发送一个自定义的广播,当收到广播的时候,重新启动service;
3. 依靠第三方
  • 根据终端不同,在小米手机(包括 MIUI)接入小米推送、华为手机接入华为推送;其他手机可以考虑接入腾讯信鸽或极光推送与小米推送做 A/B Test。

参考

我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-01-08 14:08:36  更:2022-01-08 14:09:06 
 
开发: 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/24 10:32:08-

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