Android的服务(Service)(一)生命周期

本篇和接下来的几篇我们来浅析一下Android的另外一个非常重要的组件:Service,看到这里我们的脑海里都会涌现出什么词语呢?诸如:无用户交互界面,耗时后台操作,服务(级别)进程,远程调用。
1、看看Service的代码,好干净的感觉,没错,它就定义了一些生命周期的方法以及一些成员,注意这些成员中并没有Window,所以Service是没有用户界面的。
2、Service能进行后台耗时操作只是因为她的进程级别,并不是因为这个组件本身,因为执行后台操作的根本是工作线程。做应用和系统的都很了解进程的五个级别。那就是前台进程,可见进程,服务进程,后台进程和空进程。可以这么说:Service的进程级别永远是服务进程级别和以上级别。
3、远程调用是一种完美实现CS模型的设计,整个系统Android系统中充斥着各种Service,比如ActivityManagerService。而我们的应用程序都是一个客户端使用这些服务提供的功能时就是远程调用。这里有两种调用,一种是应用开发常用的bindService方式也就是AIDL,还有一个当然也是这种代理模式的实现,但是不同的是获取到其本地代理的方式不同,它通过ServiceManager来间接获得。

以上只是关于Service的一些概述的东西,下面将参考源码将我们常说Service的一些特性进行说明:

(一)、Service的生命周期

启动服务开始,ContextImpl.startService()最终是ActivityManagerService服务端的startService方法在完成实际操作,而这个操作又是交给ActiveServices
来完成的。

    ComponentName startServiceLocked(IApplicationThread caller,
            Intent service, String resolvedType,
            int callingPid, int callingUid, int userId) {
        ................
        // 检索需要启动服务的信息
        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType,
                    callingPid, callingUid, userId, true, callerFg);
        if (res == null) {
            return null;
        }
        if (res.record == null) {
            return new ComponentName("!", res.permission != null
                    ? res.permission : "private to package");
        }
        ServiceRecord r = res.record;
        
        final ServiceMap smap = getServiceMap(r.userId);
        boolean addToStarting = false;
        //下面判断当前启动服务的进程状态来确定是否需要延时启动这个服务
        if (!callerFg && r.app == null && mAm.mStartedUsers.get(r.userId) != null) {
            ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
            if (proc == null || proc.curProcState > ActivityManager.PROCESS_STATE_RECEIVER) {
                // If this is not coming from a foreground caller, then we may want
                // to delay the start if there are already other background services
                // that are starting.  This is to avoid process start spam when lots
                // of applications are all handling things like connectivity broadcasts.
                // We only do this for cached processes, because otherwise an application
                // can have assumptions about calling startService() for a service to run
                // in its own process, and for that process to not be killed before the
                // service is started.  This is especially the case for receivers, which
                // may start a service in onReceive() to do some additional work and have
                // initialized some global state as part of that.
                if (r.delayed) {
                    // This service is already scheduled for a delayed start; just leave
                    // it still waiting.
                    if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Continuing to delay: " + r);
                    return r.name;
                }
                //还有考虑到后台启动服务的数目上线
                if (smap.mStartingBackground.size() >= mMaxStartingBackground) {
                    // Something else is starting, delay!
                    Slog.i(TAG, "Delaying start of: " + r);
                    smap.mDelayedStartList.add(r);
                    r.delayed = true;
                    return r.name;
                }
                addToStarting = true;
            } else if (proc.curProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
                // We slightly loosen when we will enqueue this new service as a background
                // starting service we are waiting for, to also include processes that are
                // currently running other services or receivers.
                addToStarting = true;
                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying, but counting as bg: " + r);
            }
        }

        return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
    }
    ComponentName startServiceInnerLocked(ServiceMap smap, Intent service,
            ServiceRecord r, boolean callerFg, boolean addToStarting) {
        ProcessStats.ServiceState stracker = r.getTracker();
        if (stracker != null) {
            stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
        }
        r.callStart = false;
        synchronized (r.stats.getBatteryStats()) {
            r.stats.startRunningLocked();
        }
        String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
        if (error != null) {
            return new ComponentName("!!", error);
        }
        .................
        return r.name;
    }
    private final String bringUpServiceLocked(ServiceRecord r,
            int intentFlags, boolean execInFg, boolean whileRestarting) {
        //如果服务已经启动过了,执行以下操作
        //对于到生命周期来说,就是重复的startService不会重复执行oncreate,只是会重复执行onStartCommand
        if (r.app != null && r.app.thread != null) {
            sendServiceArgsLocked(r, execInFg, false);
            return null;
        }
        .......................
        // Service is now being launched, its package can't be stopped.
        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);
        }

        final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
        final String procName = r.processName;
        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, mAm.mProcessStats);
                    realStartServiceLocked(r, app, execInFg);
                    return null;
                } catch (RemoteException e) {
                    Slog.w(TAG, "Exception when starting service " + r.shortName, e);
                }

                // If a dead object exception was thrown -- fall through to
                // restart the application.
            }
        }
        ......................
        return null;
    } 
    private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app, boolean execInFg) throws RemoteException {
        if (app.thread == null) {
            throw new RemoteException();
        }
        if (DEBUG_MU)
            Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid
                    + ", ProcessRecord.uid = " + app.uid);
        r.app = app;
        r.restartTime = r.lastActivity = SystemClock.uptimeMillis();

        app.services.add(r);
        bumpServiceExecutingLocked(r, execInFg, "create");
        mAm.updateLruProcessLocked(app, false, null);
        mAm.updateOomAdjLocked();

        boolean created = false;
        //这是服务的首次启动流程,先执行scheduleCreateService,其实到了本地端就是实例化服务类,然后调用了其onCreate方法
        try {
            String nameTerm;
            int lastPeriod = r.shortName.lastIndexOf('.');
            nameTerm = lastPeriod >= 0 ? r.shortName.substring(lastPeriod) : r.shortName;
            EventLogTags.writeAmCreateService(
                    r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
            synchronized (r.stats.getBatteryStats()) {
                r.stats.startLaunchedLocked();
            }
            mAm.ensurePackageDexOpt(r.serviceInfo.packageName);
            //调整进程的状态也就是优先级了
            app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
            app.thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                    app.repProcState);
            r.postNotification();
            created = true;
        } finally {
            if (!created) {
                app.services.remove(r);
                r.app = null;
                scheduleServiceRestartLocked(r, false);
            }
        }
        //这里是处理bind服务的请求
        requestServiceBindingsLocked(r, execInFg);

        // If the service is in the started state, and there are no
        // pending arguments, then fake up one so its onStartCommand() will
        // be called.
        if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
            r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                    null, null));
        }
        //然后再执行后续的sendServiceArgsLocked方法
        sendServiceArgsLocked(r, execInFg, true);

        if (r.delayed) {
            if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r);
            getServiceMap(r.userId).mDelayedStartList.remove(r);
            r.delayed = false;
        }

        if (r.delayedStop) {
            // Oh and hey we've already been asked to stop!
            r.delayedStop = false;
            if (r.startRequested) {
                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (from start): " + r);
                stopServiceLocked(r);
            }
        }
    }
    private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
            boolean oomAdjusted) {
        //这个方法在后面流程可以注意到,在bindservice时也有进来,所以呢,这个pendingStarts就成了是否执行实际操作的判断条件
        //直接采用绑定方式创建服务时这个是没有的,start流程中才有添加过程
        final int N = r.pendingStarts.size();
        if (N == 0) {
            return;
        }
        
        while (r.pendingStarts.size() > 0) {
            try {
                ServiceRecord.StartItem si = r.pendingStarts.remove(0);
                //这里scheduleServiceArgs方法调用的是本地端服务的onStartCommand方法,这里的本地端是相对于ActivityManagerService来说的。
                r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
            } catch (RemoteException e) {
                // Remote process gone...  we'll let the normal cleanup take
                // care of this.
                if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while scheduling start: " + r);
                break;
            } catch (Exception e) {
                Slog.w(TAG, "Unexpected exception", e);
                break;
            }
        }
    }    
    从start的启动流程看,得到结论是正常启动会先执行onCreate然后执行onStartCommand,重复启动服务只是会重复执行onStartCommand,并不会多次执行onCreate。特殊的是,如果之前有客户端绑定但是未自动创建服务的情况下,startService会执行完onCreate后执行onBind最后是onStartCommand,注意后面介绍中的绑定流程。
    下面来看下一种情况,直接bindservice看看又是如何执行的呢,其他调用流程省略,只看关键的部分
    int bindServiceLocked(IApplicationThread caller, IBinder token,
            Intent service, String resolvedType,
            IServiceConnection connection, int flags, int userId) {
        ....................
        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType,
                    Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg);
        ....................        
        try {
            if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) {
                if (DEBUG_SERVICE) Slog.v(TAG, "BIND SERVICE WHILE RESTART PENDING: "
                        + s);
            }
            ...................
<pre name="code" class="java">            //bindings中添加一起绑定请求,后续requestServiceBindingsLocked()流程中处理绑定接口
            AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
            ....................
            if ((flags&Context.BIND_AUTO_CREATE) != 0) {
                s.lastActivity = SystemClock.uptimeMillis();
                //如果携带的标志位中包含自动启动,则进行创建服务的操作,代码可以看前面,如果已经启动了,其实是什么操作也不干的
                if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {
                    return 0;
                }
            }

            if (s.app != null) {
                // This could have made the service more important.
                mAm.updateLruProcessLocked(s.app, s.app.hasClientActivities, b.client);
                mAm.updateOomAdjLocked(s.app);
            }
            
            if (s.app != null && b.intent.received) {
                // Service is already running, so we can immediately
                // publish the connection.
                // 如果服务已经启动并且有绑定过了,直接返回binder对象,这个放到后面跨进程部分来说
                try {
                    c.conn.connected(s.name, b.intent.binder);
                } catch (Exception e) {
                    Slog.w(TAG, "Failure sending service " + s.shortName
                            + " to connection " + c.conn.asBinder()
                            + " (in " + c.binding.client.processName + ")", e);
                }

                // If this is the first app connected back to this binding,
                // and the service had previously asked to be told when
                // rebound, then do so.
                // 从这里可以看出,一般情况下,onBind只会执行一次,除非请求doRebind
                // 这个标志位是旧的客户端全部unbind之后自动设置上的,这个后面将Unbind的时候会看到
                if (b.intent.apps.size() == 1 && b.intent.doRebind) {
                    requestServiceBindingLocked(s, b.intent, callerFg, true);
                }
            } else if (!b.intent.requested) {
                //服务还没有绑定者,则执行后续操作将调用到onBind操作
                requestServiceBindingLocked(s, b.intent, callerFg, false);
            }

            getServiceMap(s.userId).ensureNotStartingBackground(s);

        } finally {
            Binder.restoreCallingIdentity(origId);
        }

        return 1;
    }

 
    private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) {
        //这个方法在正常的start流程中也有执行到,就得靠bindings来作为判断执行条件,这里面存的是bindservice的请求,只有在bind服务时才会添加
        for (int i=r.bindings.size()-1; i>=0; i--) {
            IntentBindRecord ibr = r.bindings.valueAt(i);
            if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {
                break;
            }
        }
    }
    private final boolean requestServiceBindingLocked(ServiceRecord r,
            IntentBindRecord i, boolean execInFg, boolean rebind) {
        if (r.app == null || r.app.thread == null) {
            // If service is not currently running, can't yet bind.
            return false;
        }
        if ((!i.requested || rebind) && i.apps.size() > 0) {
            try {
                bumpServiceExecutingLocked(r, execInFg, "bind");
                r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
                // 哇,又要去本地执行onBind方法咯
                r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
                        r.app.repProcState);
                if (!rebind) {
                    i.requested = true;
                }
                i.hasBound = true;
                i.doRebind = false;
            } catch (RemoteException e) {
                if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while binding " + r);
                return false;
            }
        }
        return true;
    }    
        好了,到这里我们已经有了一定的结论,直接绑定服务时会先执行onCreate,然后执行onBind但不会执行onStartCommand(注意前面介绍中的sendServiceArgsLocked()方法注释);已经启动的服务直接执行onBind。剩下的bind后面的事情就留在讲解后面两个问题的时候再说吧就是返回binder对象和如何跨进程的事情。
       其他的生命周期分析方式类似,这里边有一个典型操作:绑定的服务先stopService然后unbind的执行次序,没开始之前可以猜一下结果。
执行stop操作执行到这里来
    private void stopServiceLocked(ServiceRecord service) {
        if (service.delayed) {
            // If service isn't actually running, but is is being held in the
            // delayed list, then we need to keep it started but note that it
            // should be stopped once no longer delayed.
            if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Delaying stop of pending: " + service);
            service.delayedStop = true;
            return;
        }
        ..............
        service.startRequested = false;
        service.callStart = false;
        bringDownServiceIfNeededLocked(service, false, false);
    }
    private final void bringDownServiceIfNeededLocked(ServiceRecord r, boolean knowConn,
            boolean hasConn) {
        // 先判断是否还需要这个服务存在
        if (isServiceNeeded(r, knowConn, hasConn)) {
            return;
        }
        // Are we in the process of launching?
        if (mPendingServices.contains(r)) {
            return;
        }
        bringDownServiceLocked(r);
    }   
    private final boolean isServiceNeeded(ServiceRecord r, boolean knowConn, boolean hasConn) {
        // Are we still explicitly being asked to run?
        if (r.startRequested) {
            return true;
        }
        // Is someone still bound to us keepign us running?
        // 这个knowConn变量名起的有意思,是否知道有人连接,后面可以看到在解除绑定的时候再来这里,这个值就是true的
        if (!knowConn) {
            // 如果不知道是否有人绑定,则去获取一下是否有,这里很重要,大家看获取的是AutoCreate,也就是说绑定时带了Context.BIND_AUTO_CREATE标志位来的
            // 更多根其相关的r.bindings,这个在前面也有提出,在正常的start流程中执行oncreate之后会检测这个然后去执行onBind操作。
            hasConn = r.hasAutoCreateConnections();
        }
        if (hasConn) {
            //逻辑就是如果还有人绑定着就需要服务存在,bringDownServiceIfNeededLocked()这个方法也就直接返回了。
            return true;
        }
        return false;
    }
    private final void bringDownServiceLocked(ServiceRecord r) {
        // Report to all of the connections that the service is no longer
        // available.
        for (int conni=r.connections.size()-1; conni>=0; conni--) {
            ArrayList<ConnectionRecord> c = r.connections.valueAt(conni);
            for (int i=0; i<c.size(); i++) {
                ConnectionRecord cr = c.get(i);
                // There is still a connection to the service that is
                // being brought down.  Mark it as dead.
                cr.serviceDead = true;
                try {
                    cr.conn.connected(r.name, null);
                } catch (Exception e) {
                    Slog.w(TAG, "Failure disconnecting service " + r.name +
                          " to connection " + c.get(i).conn.asBinder() +
                          " (in " + c.get(i).binding.client.processName + ")", e);
                }
            }
        }

        // Tell the service that it has been unbound.
        if (r.app != null && r.app.thread != null) {
            for (int i=r.bindings.size()-1; i>=0; i--) {
                IntentBindRecord ibr = r.bindings.valueAt(i);
                if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down binding " + ibr
                        + ": hasBound=" + ibr.hasBound);
                if (ibr.hasBound) {
                    try {
                        bumpServiceExecutingLocked(r, false, "bring down unbind");
                        mAm.updateOomAdjLocked(r.app);
                        ibr.hasBound = false;
                        r.app.thread.scheduleUnbindService(r,
                                ibr.intent.getIntent());
                    } catch (Exception e) {
                        Slog.w(TAG, "Exception when unbinding service "
                                + r.shortName, e);
                        serviceProcessGoneLocked(r);
                    }
                }
            }
        }
        ..................
        unscheduleServiceRestartLocked(r, 0, true);
        // Also make sure it is not on the pending list.
        for (int i=mPendingServices.size()-1; i>=0; i--) {
            if (mPendingServices.get(i) == r) {
                mPendingServices.remove(i);
                if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r);
            }
        }
        ..................
        // Clear start entries.
        r.clearDeliveredStartsLocked();
        r.pendingStarts.clear();

        if (r.app != null) {
            synchronized (r.stats.getBatteryStats()) {
                r.stats.stopLaunchedLocked();
            }
            r.app.services.remove(r);
            if (r.app.thread != null) {
                updateServiceForegroundLocked(r.app, false);
                try {
                    bumpServiceExecutingLocked(r, false, "destroy");
                    mDestroyingServices.add(r);
                    mAm.updateOomAdjLocked(r.app);
                    r.app.thread.scheduleStopService(r);
                } catch (Exception e) {
                    Slog.w(TAG, "Exception when destroying service "
                            + r.shortName, e);
                    serviceProcessGoneLocked(r);
                }
            }
            ....................
        }
        ......................
        if (r.bindings.size() > 0) {
            r.bindings.clear();
        }
        if (r.restarter instanceof ServiceRestarter) {
           ((ServiceRestarter)r.restarter).setService(null);
        }
        .....................
    }
 再来看看解除绑定的操作时哪些
    boolean unbindServiceLocked(IServiceConnection connection) {
        IBinder binder = connection.asBinder();
        ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);
        if (clist == null) {
            Slog.w(TAG, "Unbind failed: could not find connection for "
                  + connection.asBinder());
            return false;
        }
        final long origId = Binder.clearCallingIdentity();
        try {
            while (clist.size() > 0) {
                ConnectionRecord r = clist.get(0);
                removeConnectionLocked(r, null, null);
                if (r.binding.service.app != null) {
                    // This could have made the service less important.
                    mAm.updateOomAdjLocked(r.binding.service.app);
                }
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
        return true;
    }  
    void removeConnectionLocked(
        ConnectionRecord c, ProcessRecord skipApp, ActivityRecord skipAct) {
        ...................
        //这里在没解绑一个就是取消一个connection
        //这个在本方法的最后,如果所有的都解绑connections为空,s.hasAutoCreateConnections()返回值是false的,
        //这个在判断服务是否还需要保留时会判断为不保留直接销毁
         ArrayList<ConnectionRecord> clist = s.connections.get(binder);
        if (clist != null) {
            clist.remove(c);
            if (clist.size() == 0) {
                s.connections.remove(binder);
            }
        }
        b.connections.remove(c);
        if (c.activity != null && c.activity != skipAct) {
            if (c.activity.connections != null) {
                c.activity.connections.remove(c);
            }
        }
        ..................
        if (!c.serviceDead) {
            if (DEBUG_SERVICE) Slog.v(TAG, "Disconnecting binding " + b.intent
                    + ": shouldUnbind=" + b.intent.hasBound);
            if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0
                    && b.intent.hasBound) {
                try {
                    bumpServiceExecutingLocked(s, false, "unbind");
                    if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0
                            && s.app.setProcState <= ActivityManager.PROCESS_STATE_RECEIVER) {
                        // If this service's process is not already in the cached list,
                        // then update it in the LRU list here because this may be causing
                        // it to go down there and we want it to start out near the top.
                        mAm.updateLruProcessLocked(s.app, false, null);
                    }
                    mAm.updateOomAdjLocked(s.app);
                    b.intent.hasBound = false;
                    // Assume the client doesn't want to know about a rebind;
                    // we will deal with that later if it asks for one.
                    b.intent.doRebind = false;
                    s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent());
                } catch (Exception e) {
                    Slog.w(TAG, "Exception when unbinding service " + s.shortName, e);
                    serviceProcessGoneLocked(s);
                }
            }

            if ((c.flags&Context.BIND_AUTO_CREATE) != 0) {
                boolean hasAutoCreate = s.hasAutoCreateConnections();
                if (!hasAutoCreate) {
                    if (s.tracker != null) {
                        s.tracker.setBound(false, mAm.mProcessStats.getMemFactorLocked(),
                                SystemClock.uptimeMillis());
                    }
                }
                //如果是绑定时创建的,还需要看看是否需要执行stop。
                bringDownServiceIfNeededLocked(s, true, hasAutoCreate);
            }
        }
    }
好了,看完应该也有结论了,那就是如果服务是先创建,然后被绑定的,直接stop后,会通知每个绑定客户端服务将销毁,然后执行onUnbind,最后执行onDestroy销毁服务

如果是绑定时创建的服务,直接stop是不会销毁的,直到所有的带有auto_create标志的客户端都解除绑定之后会直接执行onDestroy进行销毁。一句话:服务的各种异样死法都跟这个标志Context.BIND_AUTO_CREATE有关,有了它,管你谁先谁后我都是老大,没有它start操作才最NB,因为单纯bind压根起不来还得等待start来。

到这里我觉得生命周期的一些特殊问题已经阐述的差不多了,在上面的代码中时不时就会出现关于ReStart的字眼,这就是我下篇将要说的一个问题,服务的重启问题。

后续内容预告:

(二)、Service的自动重启问题

(三)、Service与其客户端的绑定如何实现,即跨进程调用问题。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值