Service启动过程源码分析(Android Q)

Service的启动概述

本章我们来分析Service的启动过程。

Service生命周期

先来看下Service的生命周期:

startService和bindService启动的服务生命周期略有不同。

Service启动方式

我们启动一个Serivce服务的时候,可以通过3种方式来启动:

  • startService()方法,启动一个Service。
  • bindService()方法,使用bind的方式启动一个Service。
  • startForegroundService()方法,启动一个前台服务。

这几种启动方式,定义在Activity的父类型ContextWrapper类中,它们都通过调用mBase来执行具体实现,这里的mBase,就是一个ContextImpl实例对象。

startService启动——Client进程端

ContextImpl的相关实现

service启动的几个方法:

/frameworks/base/core/java/android/app/ContextImpl.java

    @Override
    public ComponentName startService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, false, mUser);
    }

    @Override
    public ComponentName startForegroundService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, true, mUser);
    }
    
    @Override
    public boolean bindService(Intent service, ServiceConnection conn,
            int flags) {
        warnIfCallingFromSystemProcess();
        return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), getUser());
    }
逻辑解析:
  1. startService和startForegroundService都调用了startServiceCommon方法,只是第二个参数不同(前台服务是true)。
  2. bindService实现稍有不同,这里调用了bindServiceCommon方法。

我们重点来分析startService的后续流程。

来看startServiceCommon方法:

    private ComponentName startServiceCommon(Intent service, boolean requireForeground,
            UserHandle user) {
        try {
            validateServiceIntent(service);
            service.prepareToLeaveProcess(this);
            ComponentName cn = ActivityManager.getService().startService(
                mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                            getContentResolver()), requireForeground,
                            getOpPackageName(), user.getIdentifier());
            if (cn != null) {
                if (cn.getPackageName().equals("!")) {
                    throw new SecurityException(
                            "Not allowed to start service " + service
                            + " without permission " + cn.getClassName());
                } else if (cn.getPackageName().equals("!!")) {
                    throw new SecurityException(
                            "Unable to start service " + service
                            + ": " + cn.getClassName());
                } else if (cn.getPackageName().equals("?")) {
                    throw new IllegalStateException(
                            "Not allowed to start service " + service + ": " + cn.getClassName());
                }
            }
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
逻辑解析:
  1. 验证Intent相关设置,如果Service是隐式启动,则在Android L之上会抛出错误,在小于该版本输出提示日志但不报错。
  2. 判断是否将要离开当前进程,并且设置Intent相关属性。
  3. 调用AMS的startService方法来启动Service,在此之后,代码的执行由Client端切换到了系统服务AMS进程。

startService启动——Server进程端

AMS的startService方法

AMS的startService方法:

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

    @Override
    public ComponentName startService(IApplicationThread caller, Intent service,
            String resolvedType, boolean requireForeground, String callingPackage, int userId)
            throws TransactionTooLargeException {
        enforceNotIsolatedCaller("startService"); //判断是否是隔离的程序,如果是,则不允许执行,抛出错误
        ……
        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, userId);
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
            return res;
        }
    }
逻辑解析:
  1. 执行一些简单的验证工作,例如判断是否是隔离程序,调用者的包名是否为null等。
  2. 调用mServices.startServiceLocked执行启动过程。这里的mServices是ActiveServices的一个实例,用来管理Serivice的。

ActiveServices的startServiceLocked方法

ActiveServices的startServiceLocked方法:

/frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

    ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
            int callingPid, int callingUid, boolean fgRequired, String callingPackage,
            final int userId, boolean allowBackgroundActivityStarts)
            throws TransactionTooLargeException {
        ……
        //判断当前调用进程是否属于前台进程组(进程组优先级!= ProcessList.SCHED_GROUP_BACKGROUND)
        final boolean callerFg;
        if (caller != null) {
            final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
            if (callerApp == null) {
                throw new SecurityException(
                        "Unable to find app for caller " + caller
                        + " (pid=" + callingPid
                        + ") when starting service " + service);
            }
            callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
        } else {
            callerFg = true;
        }
        //调用retrieveServiceLocked方法来获取一个ServiceLookupResult对象。
        ServiceLookupResult res =
            retrieveServiceLocked(service, null, resolvedType, callingPackage,
                    callingPid, callingUid, userId, true, callerFg, false, false);
        ……

        ServiceRecord r = res.record; //从ServiceLookupResult对象中,取出ServiceRecord对象,以进行后续操作。

        ……
        //处理、校验启动权限等相关的逻辑,并且判断将要启动的服务是否是前台服务等。
        ……
        // At this point we've applied allowed-to-start policy based on whether this was
        // an ordinary startService() or a startForegroundService().  Now, only require that
        // the app follow through on the startForegroundService() -> startForeground()
        // contract if it actually targets O+.
        if (r.appInfo.targetSdkVersion < Build.VERSION_CODES.O && fgRequired) {
            if (DEBUG_BACKGROUND_CHECK || DEBUG_FOREGROUND_SERVICE) {
                Slog.i(TAG, "startForegroundService() but host targets "
                        + r.appInfo.targetSdkVersion + " - not requiring startForeground()");
            }
            fgRequired = false; //fgRequired表示是否需要执行startForeground来启动Service。
        }

        //对是否需要启动前弹框进行判断
        NeededUriGrants neededGrants = mAm.mUgmInternal.checkGrantUriPermissionFromIntent(
                callingUid, r.packageName, service, service.getFlags(), null, r.userId);

        ……

        ……
        if (fgRequired) { //启动前台服务
            // We are now effectively running a foreground service.
            ServiceState stracker = r.getTracker();
            if (stracker != null) {
                stracker.setForeground(true, mAm.mProcessStats.getMemFactorLocked(),
                        r.lastActivity);
            }
            mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, true);
        }

        ……

        if (allowBackgroundActivityStarts) { //如果允许从后台Activity启动
            r.whitelistBgActivityStartsOnServiceStart();
        }
        //执行常规的启动流程
        ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
        return cmp;
    }
逻辑解析:
  1. 判断当前调用者是否属于前台进程组(进程组优先级!= ProcessList.SCHED_GROUP_BACKGROUND)。
  2. 调用retrieveServiceLocked方法来获取一个ServiceLookupResult对象。retrieveServiceLocked首先从缓存列表中查找是否已经存在该Service记录,如果不存在,则从包资源里解析获取,最终生成一个含有Service信息的ServiceLookupResult对象。
  3. 从ServiceLookupResult对象中,取出ServiceRecord对象,以进行后续操作。
  4. 处理、校验启动权限等相关的逻辑,并且判断将要启动的服务是否是前台服务等。
  5. fgRequired表示是否需要执行startForeground来启动Service。
  6. 对是否需要启动前弹框进行判断。
  7. 如果是前台服务,则执行启动前台服务。
  8. 调用startServiceInnerLocked执行常规的启动流程。

ActiveServices的startServiceInnerLocked方法

    ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
            boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
        ServiceState stracker = r.getTracker();
        if (stracker != null) {
            stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);//更新ServiceRecord的ServiceState
        }
        r.callStart = false;
        StatsLog.write(StatsLog.SERVICE_STATE_CHANGED, r.appInfo.uid, r.name.getPackageName(),
                r.name.getClassName(), StatsLog.SERVICE_STATE_CHANGED__STATE__START);
        synchronized (r.stats.getBatteryStats()) {
            r.stats.startRunningLocked();//用于耗电统计,开启运行的状态
        }
        //service启动的核心方法
        String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false);
        if (error != null) {
            return new ComponentName("!!", error);
        }

        if (r.startRequested && addToStarting) {
            boolean first = smap.mStartingBackground.size() == 0;//是否是当前用户进程的第一个服务
            smap.mStartingBackground.add(r);
            r.startingBgTimeout = SystemClock.uptimeMillis() + mAm.mConstants.BG_START_TIMEOUT;
            if (DEBUG_DELAYED_SERVICE) {
                RuntimeException here = new RuntimeException("here");
                here.fillInStackTrace();
                Slog.v(TAG_SERVICE, "Starting background (first=" + first + "): " + r, here);
            } else if (DEBUG_DELAYED_STARTS) {
                Slog.v(TAG_SERVICE, "Starting background (first=" + first + "): " + r);
            }
            if (first) {
                smap.rescheduleDelayedStartsLocked();//执行延迟启动
            }
        } else if (callerFg || r.fgRequired) {
            smap.ensureNotStartingBackgroundLocked(r);//清楚map数据
        }

        return r.name;
    }
逻辑解析:
  1. 调用service的bringUpServiceLocked方法来启动Service。
  2. 当启动完成的时候设置启动超时时间。
  3. 启动成功之后将ServiceRecord加入到smap中的mStartingBackground中。
  4. 如果是第一次启动则处理延迟启动相关逻辑。

ActiveServices的bringUpServiceLocked方法

    private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
            boolean whileRestarting, boolean permissionsReviewRequired)
            throws TransactionTooLargeException {
        if (r.app != null && r.app.thread != null) {//处理Service已经启动的情况,此时只是发送新的StartItem
            sendServiceArgsLocked(r, execInFg, false);
            return null;
        }

        if (!whileRestarting && mRestartingServices.contains(r)) { //如果Service因为crash被系统重启,则return
            // If waiting for a restart, then do nothing.
            return null;
        }

        ……
        //正在启动服务,因此不再处于重新启动状态,把service从重启服务队列中移除。
        if (mRestartingServices.remove(r)) {
            clearRestartingIfNeededLocked(r);
        }

        //确保此服务不再被视为延迟,设置r.delayed为false。
        if (r.delayed) {
            if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (bring up): " + r);
            getServiceMapLocked(r.userId).mDelayedStartList.remove(r);
            r.delayed = false;
        }

        // 确保拥有该服务的user已经启动,如果未启动,则终止操作,并执行清理工作。
        if (!mAm.mUserController.hasStartedUserState(r.userId)) {
            String msg = "Unable to launch app "
                    + r.appInfo.packageName + "/"
                    + r.appInfo.uid + " for service "
                    + r.intent.getIntent() + ": user " + r.userId + " is stopped";
            Slog.w(TAG, msg);
            bringDownServiceLocked(r);
            return msg;
        }

        // 服务正在启动,设置package停止状态为false
        try {
            AppGlobals.getPackageManager().setPackageStoppedState(
                    r.packageName, false, r.userId);
        } catch (RemoteException e) {
        } catch (IllegalArgumentException e) {
            Slog.w(TAG, "Failed trying to unstop package "
                    + r.packageName + ": " + e);
        }
        //判断当前服务是否需要在隔离进程中启动。(如果在Manifest中设置了隔离进程的标志位则该值等于标志位的值)
        final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
        final String procName = r.processName;
        HostingRecord hostingRecord = new HostingRecord("service", r.instanceName);
        ProcessRecord app;

        if (!isolated) { //常规进程中启动
            app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
            if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
                        + " app=" + app);
            if (app != null && app.thread != null) {
                try {
                    app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode, mAm.mProcessStats);
                    realStartServiceLocked(r, app, execInFg); //真正启动Service的地方
                    return null;
                }
                ……
            }
        } else {
            ……
        }

        //处理进程没有启动的情况
        if (app == null && !permissionsReviewRequired) {
            //启动service所要运行的进程
            if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                    hostingRecord, false, isolated, false)) == null) {
                String msg = "Unable to launch app "
                        + r.appInfo.packageName + "/"
                        + r.appInfo.uid + " for service "
                        + r.intent.getIntent() + ": process is bad";
                Slog.w(TAG, msg);
                bringDownServiceLocked(r);// 进程启动失败
                return msg;
            }
            if (isolated) {
                r.isolatedProc = app;
            }
        }

        ……
        if (!mPendingServices.contains(r)) {//mPendingServices保存待启动服务,当进程启动后,会重新启动该服务
            mPendingServices.add(r);
        }

        if (r.delayedStop) {//服务还未完成启动,就收到结束请求时,会直接停止该服务
            // Oh and hey we've already been asked to stop!
            r.delayedStop = false;
            if (r.startRequested) {
                if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
                        "Applying delayed stop (in bring up): " + r);
                stopServiceLocked(r);//停止服务
            }
        }

        return null;
    }
逻辑解析:
  1. 处理Service已经启动的情况,此时只是发送新的StartItem,调用service.onStartCommand()过程。
  2. 如果Service因为crash被系统重启,则return。
  3. 正在启动服务,因此不再处于重新启动状态,把service从重启服务队列中移除。
  4. 确保此服务不再被视为延迟,设置r.delayed为false。
  5. 确保拥有该服务的user已经启动,如果未启动,则终止操作,并执行清理工作。
  6. 服务正在启动,设置package停止状态为false。
  7. 判断当前服务是否需要在隔离进程中启动。(如果在Manifest中设置了隔离进程的标志位则该值等于标志位的值)
  8. 如果不是在隔离进程中启动,则调用realStartServiceLocked(r, app, execInFg); 真正启动Service。
  9. 如果进程没有启动,启动service所要运行的进程。
  10. mPendingServices保存待启动服务,当进程启动后,会重新启动该服务。

ActiveServices的realStartServiceLocked方法

即将在Client中创建并启动Service实例对象,从bindService()启动的Service也会调用这个方法。

    private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app, boolean execInFg) throws RemoteException {
        ……
        这里主要更新进程和ServiceRecord的一些信息
        
        bumpServiceExecutingLocked(r, execInFg, "create");//将service和进程关联起来(更新ServiceRecord中的信息)
        //更新进程对应的优先级
        mAm.updateLruProcessLocked(app, false, null); 
        updateServiceForegroundLocked(r.app, /* oomAdj= */ false);
        mAm.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);

        boolean created = false;
        try {
            ……
            mAm.notifyPackageUse(r.serviceInfo.packageName,
                                 PackageManager.NOTIFY_PACKAGE_USE_SERVICE);
            app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);//强制更改进程的状态为ActivityManager.PROCESS_STATE_SERVICE
            //调用Client端ApplicationThread对象的实例方法scheduleCreateService来创建Service。
            app.thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
                    app.getReportedProcState());
            r.postNotification();
            created = true;
        } catch (DeadObjectException e) {
            Slog.w(TAG, "Application dead when creating service " + r);
            mAm.appDiedLocked(app);
            throw e;
        } finally {
            if (!created) {
                // Keep the executeNesting count accurate.
                final boolean inDestroying = mDestroyingServices.contains(r);
                serviceDoneExecutingLocked(r, inDestroying, inDestroying); //执行清理工作

                // Cleanup.
                if (newService) {
                    app.services.remove(r);
                    r.setProcess(null);
                }

                // Retry.
                if (!inDestroying) {
                    scheduleServiceRestartLocked(r, false);//尝试重新启动服务
                }
            }
        }

        if (r.whitelistManager) {
            app.whitelistManager = true;
        }
        //如果调用过bindService,则会调用requestServiceBindingsLocked()执行onBind方法。
        requestServiceBindingsLocked(r, execInFg);
        //如果客户端Bind Service成功,按需更新服务端进程优先级
        updateServiceClientActivitiesLocked(app, null, true);

        if (newService && created) {
            app.addBoundClientUidsOfNewService(r);
        }

        //如果service已经启动,并且符合条件,则方法onStartCommand() 将被调用
        if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
            r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                    null, null, 0));
        }

        sendServiceArgsLocked(r, execInFg, true); 

        if (r.delayed) {//如果service是延时启动的则将其从mDelayedStartList中移除
            if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (new proc): " + r);
            getServiceMapLocked(r.userId).mDelayedStartList.remove(r);
            r.delayed = false;
        }

        if (r.delayedStop) { //如果service被主动要求停止那么调用
            // Oh and hey we've already been asked to stop!
            r.delayedStop = false;
            if (r.startRequested) {
                if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE,
                        "Applying delayed stop (from start): " + r);
                stopServiceLocked(r);
            }
        }
    }
逻辑解析:
  1. 更新进程和ServiceRecord的一些信息,并将service和进程关联起来(更新ServiceRecord中的信息)。
  2. 更新进程对应的优先级。
  3. 强制更改进程的状态为ActivityManager.PROCESS_STATE_SERVICE。
  4. 调用Client端ApplicationThread对象的实例方法scheduleCreateService来创建Service。
  5. 如果服务创建失败,则执行清理工作,并且尝试重新启动服务。
  6. 如果调用过bindService,则会调用requestServiceBindingsLocked()执行onBind方法,成功后,更新进程优先级。
  7. 如果service已经启动,并且符合条件,则方法onStartCommand() 将被调用。
  8. 如果service是延时启动的则将其从mDelayedStartList中移除。
  9. 如果service被主动要求停止那么调用。

startService启动——回到Client端

在ActiveServices的realStartServiceLocked方法中,调用了Client端ApplicationThread对象的实例方法scheduleCreateService来创建Service。

ApplicationThread对象的scheduleCreateService方法:

android.app.ActivityThread.java

        public final void scheduleCreateService(IBinder token,
                ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
            updateProcessState(processState, false);
            CreateServiceData s = new CreateServiceData();
            s.token = token;
            s.info = info;
            s.compatInfo = compatInfo;

            sendMessage(H.CREATE_SERVICE, s);
        }

这里发生给Handler一个CREATE_SERVICE的消息,我们直接来看该消息的处理方法。

    private void handleCreateService(CreateServiceData data) {
        //对将要进行GC进行处理。
        unscheduleGcIdler();
        //获得这个进程对应的LoadedApk对象
        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        Service service = null;
        try {
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            //通过反射调用,创建一个Serice对象
            service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);
        } catch (Exception e) {
            ……
        }

        try {
            ……
            //创建Service的ContextImpl对象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);
            //获取进程对应的Application对象。
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            //调用service对象的attach方法进行初始化设置
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());
            //调用Service的onCreate方法。
            service.onCreate();
            mServices.put(data.token, service);//添加服务到mServices中
            try {
                //通知AMS service启动成功,进行取消超时消息等操作
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to create service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }
    }
逻辑解析:
  1. 对将要进行GC进行处理,跳过。
  2. 获得这个进程对应的LoadedApk对象。
  3. 通过反射调用,创建一个Serice对象。
  4. 创建Service的ContextImpl对象。
  5. 获取进程对应的Application对象。
  6. 调用service对象的attach方法进行初始化设置。
  7. 调用Service的onCreate方法。
  8. 添加服务到mServices中。
  9. 通知AMS service启动成功,进行取消超时消息等操作。

到了这里,Service已经完成了启动过程。

Service的onStartCommand调用过程

那么Service的onStartCommand又是怎么样的调用流程呢?

它的起点是ActiveServices的sendServiceArgsLocked方法:

    private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
            boolean oomAdjusted) throws TransactionTooLargeException {
        ……
        try {
            r.app.thread.scheduleServiceArgs(r, slice);
        }
        ……
    }

这里调用了ApplicationThread对象的scheduleServiceArgs方法,该方法又给ActivityThread对象的Handler对象发送了H.SERVICE_ARGS消息,最终调用handleServiceArgs方法来处理该消息。

    private void handleServiceArgs(ServiceArgsData data) {
        Service s = mServices.get(data.token);
        if (s != null) {
            try {
                if (data.args != null) {
                    data.args.setExtrasClassLoader(s.getClassLoader());
                    data.args.prepareToEnterProcess();
                }
                int res;
                if (!data.taskRemoved) {
                    res = s.onStartCommand(data.args, data.flags, data.startId);
                } else {
                    s.onTaskRemoved(data.args);
                    res = Service.START_TASK_REMOVED_COMPLETE;
                }
                ……
        }
    }

这里调用了Servcie对象的onStartCommand方法。

到了这里,Service的启动逻辑相关的重要过程也就分析完了。

总结

通过本文分析,我们对startSerivce的启动流程已经了解了,这里来做一个简单的总结:

  1. 可以通过3种方式来启动一个Serivce服务:startService()方法,启动一个Service;bindService()方法,使用bind的方式启动一个Service;startForegroundService()方法,启动一个前台服务。
  2. 上述3种启动方法的是在ContextImpl中实现的。
  3. startService启动在Client进程端发起,并且进行Intent相关设置,以及一些校验工作。
  4. 然后进入到Server端(AMS所在进程),AMS使用ActiveServices类来对服务进行管理,系统服务端的主要处理工作都是在ActiveServices中进行的,最终调用到ActiveServices的realStartServiceLocked方法。
  5. ActiveServices的realStartServiceLocked方法调用了Client端ApplicationThread对象的实例方法scheduleCreateService来创建Service(回到Client端来进行Service的创建,这里的Client是Service的所在进程,不一定是启动时发起请求的Client进程了,这里需要注意)。
  6. ActiveServices的sendServiceArgsLocked方法最终调用了Service的onStartCommand方法。
原创文章 20 获赞 2 访问量 5102

猜你喜欢

转载自blog.csdn.net/u011578734/article/details/105726361
Q A
q