Android Service 详解

本文转载自:http://blog.csdn.net/niu_gao/ 

 一个Service是没有界面且能长时间运行于后台的应用组件.其它应用的组件可以启动一个服务运行于后台,即使用户切换到另一个应用也会继续运行.另外,一个组件可以绑定到一个service来进行交互,即使这个交互是进程间通讯也没问题.例如,一个aservice可能处理网络事物,播放音乐,执行文件I/O,或与一个内容提供者交互,所有这些都在后台进行.



一个service本质上可有两种表现形式:

  • Started

      一个service在某个应用组件(比如一个activity)调用startService()时就处于"started"状态(注意,可能已经启动了).一旦运行后,service可以在后台无限期地运行,即使启动它的组件销毁了.通常地,一个startedservice执行一个单一的操作并且不会返回给调用者结果.例如,它可能通过网络下载或上传一个文件.当操作完成后,service自己就停止了

  • Bound

      一个service在某个应用组件调用bindService()时就处于"bound"状态.一个boundservice提供一个client-server接口以使组件可以与service交互,发送请求,获取结果,甚至通过进程间通讯进行交叉进行这些交互.一个boundservice仅在有其它应用的组件绑定它时运行.多个应用组件可以同时绑定到一个service,但是当所有的自由竞争组件不再绑定时,service就销毁了.



  尽管这个文档是把这两种service分开讲的,但你的service可以在这两种方式下工作.它可以是started(无限期运行)同时也允许绑定.唯一的简单问题是你是否实现了一对回调方法:onStartCommand()允许组件启动它并且onBind()允许绑定.



  不论你是应用是否启动,或绑定到一个服务或两者都做了,任何应用组件都可以使用service(即使从另一个应用),跟任何组件都可以使用activity一样通过一个Intent启动它.然而,你可以在manifest文件中声明服务为私有,并且阻止另外的应用访问它.这在讲如何于manifest文件中声明service时会详细讲解.

  注意:一个service是运行在它所在进程的主线程中的service不会创建它自己的thread也不会运行于单独的进程(除非你另外指定).这表示,如果你的service想做一些狂耗CPU的工作或阻塞型的操作(比如MP3播放或网络通讯),你必须在service中创建一个新的线程来做那些工作.通过使用一个分离的线程,你将减少"应用没有反应"(ANR)错误并且应用的主线程可以保持activity对用户操作的快速反应.



Android Service 详解一:基础

你应使用一个service还是线程?

  一个service是一个在用户不与你的应用交互时依然可以运行于后台的简单组件.所以,只有你需要这样做时才创建一个service

  如果你需要执行的工作不在主线程中,但是只有用户在与你的应用交互时才进行,那么你可能应该创建一个新的线程而不是一个service.例如,如果你想播放一些音乐,但是只在你的activity运行时才播放,你应该在onCreate()中创建一个线程,在onStart()运行这个线程,然后在onStop()中停止它.也可以考虑使用AsyncTaskHandlerThread,来代替传统的线程类.

  记住,如果你使用了service,它默认会固定运行于你的应用的主线程,所以你应该在其中创建一个线程来执行耗时或阻塞的操作.



  要创建一个service,你必须创建一个Service(或某个已存在的子类)的子类.在你的实现中,你应覆写一些处理有关service生命期的关键方面的回调方法并且提供一个能让组件绑定到service的机制(如果需要).你应覆写的最重要的回调方法是:

  • onStartCommand()

      系统在其它组件比如activity通过调用startService()请求service启动时调用这个方法.一旦这个方法执行,service就启动并且在后台长期运行.如果你实现了它,你需要负责在service完成任务时停止它,通过调用stopSelf()stopService()(如果你只想提供绑定,你不需实现此方法)

  • OnBind()

      当组件调用bindService()想要绑定到service(比如想要执行进程间通讯)系统调用此方法.在你的实现中,你必须提供一个返回一个IBinder来以使客户端能够使用它与service通讯,你必须总是实现这个方法,但是如果你不允许绑定,那么你应返回null

  • OnCreate()

      系统在service第一次创建时执行此方法,来执行只运行一次的初始化工作(在调用它方法如onStartCommand()onBind()之前).如果service已经运行,这个方法不会被调用.

  • OnDestroy()

      系统在service不再被使用并要销毁时调用此方法.你的service应在此方法中释放资源,比如线程,已注册的侦听器,接收器等等.这是service收到的最后一个调用.



  如果一个组件通过调用startService()启动一个service(最终导致onStartCommand()被调用),之后service会保持运行,直到它通过stopSelf()停止自己或另外的组件调用stopService()停止它.



  如果一个组件调用bindService()来创建service(onStartCommand()不会被调用),那么service只是运行在绑定期间.一旦service从所有的客户端解除绑定,系统就会杀了它.



  Android系统只在内存很少并且必须为具有用户焦点的actvity釋放资源时才会强制停止一个service.如果service是绑定到具有用户焦点的activity上,那么它很难被杀死,并且如果service被声明为运行于前台(后面将讨论),那么它将永不被杀死,除非,如果这个service启动并且长期运行,那么系统将会降低它在后台任务超时列表中的位置然后这个将变成高度易被杀对象如果你的service被启动,那么它必须被设计为能优雅地处理被系统重启的操作.如果系统杀死了你的service,它会在资源重新可用时立马重启它(但是依赖于你在onStartCommand()中的返回值).关于系统何时杀死一个service的更多信息,请看:android 进程与线程详解

manifest中声明一个service

  跟activity以及其它组件一样,你必须在你的应用的manifest文件中声明所有的service们.

  要声明你的service,添加一个<service>元素作为<application>元素的儿子.例如:

<span style="font-size:14px;"><manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest></span>

  有许多属性你可以包含在<service>元素中,比如启动service的权限和service运行所在的进程.android:name属性是哇一必须的它指定了service类的名字.一旦你发布了你的应用,你不应再改变这个名字,因为如果你改了,你可能使一些通过明确的intent来引用你的service的功能无法运行.

  就像一个activity,一个service可以定义intent过滤器来使得其它组件使用明确的intent调用自己.通过声明intent过滤器,你设备上的任意应用中的组件都可以通过给startService()传递匹配的intent来启动你的sevice

  如果你打算只在本应用内使用自己的service,那么你不需指定任何intent过滤器.不使用intent过滤器,你必须使用一个明确指定service的类名的intent来启动你的service

  另外,你也可以通过包含android:exported属性,并指定其值为”false”来保证你的service是私有的.即使你的service使用了intent过滤器,也会起作用.

创建一个"启动的"Service

  针对Android1.6或更早的版本:

  如果你创建的应用是针对Android1.6或更早版本的,你需要实现onStart()而不是onStartCommand()(Android2.0中,onStart()被废弃代替之以onStartCommand())

  更多关于如何兼容2.0之前版本的知识,请看onStartCommand()文档.


  一个启动的service,在被其它组件调用startService()来启动时,会导致serviceonStartCommand()方法被调用.

  当一个service被启动后,它的生命期就不再依赖于启动它的组件并且可以独立运行于后台,即使启动它的组件死翘翘了.所以,service应该工作完成后调用stopSelf()自己停止掉,或者其它组件也可以调用stopService()停止service


  一个应用组件,比如一个activity可以通过调用startService()启动service同时传递一个指定serviceservice所用的数据的Intentservice在方法onStartCommand()中接收这个Intent

  例如,假设一个activity需要保存一些数据到一个在线数据库中.这个activity可以通过传递一个intentstartService()来启动一个service并且把数据传给service来让servcie存储它们.serviceonStartCommand()接收intent,连接到Internet然后执行数据库事物.当事物完成后,service停止自己然后被销毁.


  小心:service默认运行在声明它的应用进程的主线程中.所以,如果你的service执行密集运算或阻塞操作并且与跟用户交互的activity位于相同的应用中,这个service将会拉低activity的性能.要避免影响应用的性能,你必须在service中启动一个线程.


  传统上,有两个类你可以从它派生来创建"启动的"service

  • Service

      这是所有service的基类.当你派生这个类时,在service中创建一个新的线程来做所有的工作是十分重要的.因为这个service会默认使用你的应用的主线程,这将拉低你的应用中所有运行的activity的性能

  • IntentService

      这是一个Service的子类,使用一个工作线程来处理所有的启动请求,一次处理一个.这是你不需你的service同时处理多个请求时的最好选择.你所有要做的就是实现onHandleIntent(),这个方法接收每次启动请求发来的intent,于是你可以做后台的工作.

IntentService类派生

  因为大多数"启动的"service不需要同时处理多个请求,可能从IntentService实现你的service是最好的选择.


IntentService做了以下工作:

  • 创建一个默认的工作线程在主线程之外执行所有派发到onStartCommand()intent

  • 创建一个工作队列,某个时间只传递一个intent到你的onHandleIntent()实现中,于是你不必担心多线程的问题.

  • 当所有开始的请求都处理后,停止service,所以你永远不需调用stopSelf()

  • 提供onBind()的默认实现,返回null

  • 提供一个onStartCommand()的默认实现,把intent加入到工作队列之后会传给你的onHandleIntent()实现.

  以上实现使得你可以仅仅实现onHandleIntent()来做要做的工作即可.(当然,你还是要实现一个小小的构造函数)


下面是一个实现IntentService的例子:

<span style="font-size:14px;">public class HelloIntentService extends IntentService {

  /** 
   * 一个构造函数是必须的,并且你必须调用父类的IntentService(String)以传入工作线程的名字.
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * IntentService在默认的工作线程中调用这个方法</span><p><span style="font-size:14px;">   *当这个方法返回后,IntentService停止服务,如果能停止的话.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      long endTime = System.currentTimeMillis() + 5*1000;
      while (System.currentTimeMillis() < endTime) {
          synchronized (this) {
              try {
                  wait(endTime - System.currentTimeMillis());
              } catch (Exception e) {
              }
          }
      }
  }
}</span></p>

  以上就是你所有需要做的:一个构造函数和一个onHandleIntent()的实现.

  如果你决定重写其它的方法,比如onCreate()onStartCommand()oronDestroy(),要保证调用父类的对应实现,这样IntentService才能正确地处理工作线程的生命期.

比如,onStartCommand()必须返回默认的实现(其中实现了intent被传送到onHandleIntent()的逻辑)

<span style="font-size:14px;">@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
    return super.onStartCommand(intent,flags,startId);
}</span>

  除onHandleIntent()外,唯一不需调用父类实现的方法是onBind()(但是你只需在你的service允许绑定时才实现它)

  在下一节,你将看到同样的service类,从类Service派生时是如何实现的.这需要写更多的代码,但是当你需要处理同时发生的请求时(非序列化)这就是合适的做法了.

Android Service 详解三:从类Service派生service

从类 Service 派生

  如你在上节所见,使用类IntentService使得你实现一个"开始的"service非常容易.然而,如果你需要你的service以多线程方式执行(而不是使用工作队列),那么你需要从类Service派生来处理每个intent



  相比之下,下面的例子从类Service派生并实现了与上面使用IntentService例子完全相同的工作.也就是在一个线程中序列化的处理每个"开始"请求.

<span style="font-size:14px;">public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  // 处理从线程收到的消息们
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // 通常我们在这里做一些工作比如下载一个文件
          // 在我们的例子中,仅仅是睡5秒钟.
          long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
          // 使用startId停止服务,从而使我们不会在处理
          // 另一个工作的中间停止service
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // 启动运行service的线程.注意我创建了一个
    // 分离的线程,因为service通常都是在进程的
    // 主线程中运行,但我们不想让主线程阻塞.我们还把新线程
    // 搞成后台级的优先级,从而减少对UI线程(主线程的影响).
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();
    
    // Get the HandlerThread's Looper and use it for our Handler 
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // 对于每个开始请求,发送一消息来开始一次工作,并且把
      // start ID也传过去,所以当完成一个工作时,我们才知道要停止哪个请求.
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);
      
      // 如果我们在这里返回后被被杀死了,重启之.
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }
  
  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); 
  }
}
</span>

  如你所见,要做的工作比使用IntentService时多一些.



  然而,因为你自己处理每次对onStartCommand()的调用,你可以同时执行多个请求.这个例子并没有那样做,但是如果那是你所需要的,那么你可以为每个请求创建一个新的线程并且立即运行它们(而不是等待上一个请求完成)



  注意方法onStartCommand()必须返回一个整数.这个整数描述了在系统杀死它的事件中系统如何继续这个服务(如前面所述,IntentService的默认实现为你处理这些,当然你也能够去改写它)onStartCommand()也返回值必须是下面常量之一:

  • START_NOT_STICKY

      如果系统在onStartCommand()返回后杀死了服务,不要重新创建这个service,除非还有挂起的intent需要被传送.这是避免在不必要时运行你的service和当你的应用可以简单重启任何未竟的工作时的最佳选择.

  • START_STICKY

      如果系统在onStartCommand()返回后杀死了这个service,会重新创建这个service并且调用onStartCommand(),但是不再重新发送上次最后一个intent,而是使用一个nullintent调用onStartCommand(),除非有一些挂起的intent,在此情况下,这些挂起的intent被派送.这适合于媒体播放器(or或相似也的服务),它不执行命令,但是无限期的运行并等待一个工作.

  • START_REDELIVER_INTENT

      如果系统在onStartCommand()返回后杀死了service,重新创建这个service并且使用上次最后一个intent调用onStartCommand().任何挂起的intent都顺序地被派送.这适合于活跃地执行一个工作并且应被立即恢复的服务,比如下载一个文件.


Android Service 详解四:开始停止service

开始一个Service

  你可以从一个activity或从其它应用的组件通过传递一个Intent(指定了要启动的服务)startService()启动一个服务.Android系统然后调用serviceonStartCommand()方法并且把Intent传递给它.(你永远不能直接调用onStartCommand())


  例如,一个activity可以在调用startService()时使用一个明确的intent开始前文的例子中的service(HelloSevice)

Intentintent = new Intent(this, HelloService.class);

startService(intent);


  startService()方法会立即返回然后Android系统调用serviceonStartCommand()方法.但是如果service尚没有运行,系统会先调用onCreate(),然后调用onStartCommand().


  如果service没有提供绑定功能,传给startService()intent是应用组件与service之间唯一的通讯方式.然而,如果你希望service回发一个结果,那么启动这个service的客户端可以创建一个用于广播(使用getBroadcast())PendingIntent然后放在intent中传给serviceservice然后就可以使用广播来回送结果.


  不同的启动请求导致对serviceonStartCommand()的不同调用,但停止service的请求只有一个(使用stopSelf()stopService())


停止一个service

  一个"启动的"service必须管理其自己的生命期.这表示,系统不会停止或销毁这种service,除非内存不够用了并且serviceonStartCommand()返回后会继续运行.所以,service必须调用stopSelf()停止自己或由另一个组件调用stopService()来停止它.


  一旦通过stopSelf()stopService()发出了停止请求,系统就会尽可能快地销毁service


  然而,如果你的service同时处理多个对onStartCommand()的请求,那么你不应在处理完一个请求之后就停止service,因为你可能已经又收到了新的启动请求(在第个完成后停止将会结束掉第二个).要避免这个问题,你可以使用stopSelf(int)来保证你的停止请求对应于你最近的开始请求.也就是,当你调用stopSelf(int)时,你传递开始请求的ID(传递给onStartCommand()startId)service,如果service在你调用stopSelf(int)之前收到一了个新的开始请求,发现ID不同,于是service将不会停止.


  注意:你的应用在完成工作后停止它所有的service是非常重要的.这可以避免浪费系统资源和消耗电量.如果需要,其它的组件可以调用stopService()停止service.即使你为service启用了绑定,你也必须自己停止service,甚至它收到了对onStartCommand()的调用也这样.


创建一个绑定的Service

  一个绑定的service是允许应用的组件通过调用bindService()来绑定它以创建一个能长期存在的连接(并且一般不允许组件调用startService()来启动它)


  当你的activity或其它组件想与service交互或你的应用想基于IPC的向其它应用提供功能时,你应该创建一个绑定的service


  要创建一个绑定的service,你必须实现回调方法onBind(),还要在其中返回一个IBinder,这个IBinder定义了与service通讯的接口.其它应用组件就可以在之后调用bindService()来接收这个接口并开始调用service的方法.service只在有应用组件绑定到它时才活着,所以当没有组件绑定到它时,系统就会宰了它(你不需去停止一个绑定的service,跟用onStartCommand()启动的service不一样)


  要创建一个绑定的service,首先要做的就是定义客户端如何与service通讯的接口.这个接口必须是IBinder的一个实现,并且必须被回调方法onBind()返回.一旦客户端接收到IBinder,它就可以开始与service进行交互.


  多个客户端可以一起绑定到一个service.当一个客户端完成与service的交互,它调用unbindService()来解除绑定.一旦不再有任何客户端绑定到service,系统就宰了这个service


  有很多方法来实现一个绑定的service并且这些实现要比"开始的"service难懂得多.


发送通知给用户

  一旦开始运行,一个service可以通过Toast通知或状态栏通来通知用户一些事件.


  一个toast通知是一个出现在当前窗口表面上并过一会就消失的消息.当一个状态栏通知提供一个带有消息的图标到状态栏,用就可以先定它来执行一些动作(比如启动一个activity)


  通常,一个状态栏通知是当一些后台工作(比如一个文件下载完成了)完成后通知用户可以对它进行动作的最佳方式.当用户选择这个通知时,它可以开始一个activity(比如可以查看下载的文件)

Android Service 详解五:前台运行与生命期

在前台运行 Service

  一个前台的service是被用户强烈关注的从而不会在内存低时被系统杀死.前台service必须在状态栏上提供一个通知,这个通知被放在"正在进行"区域中,这表示这个通知不能被解除,除非服务停止了或者从前台移除了.


  例如,一个从service播放音乐的音乐播放器,应被设置为前台运行,因为用户会明确地注意它的运行.在状态栏中的通知可能会显示当前的歌曲并且允许用户启动一个activity来与音乐播放器交互.

<span style="font-size:14px;">Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
        System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
        getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION, notification);</span>

  要请求你的service运行于前台,调用startForeground().此方法有两个参数:一个整数唯一的标识一个通知,和这个用于状态栏的通知,例如:


  要从前台移除service,调用stopForeground().这个方法有boolean型参数,表明是否也从状态栏删除对应的通知.这个方法不会停掉service.然而,如果你停止了正在前台运行的service,这个通知也会被删除.


  注意:方法startForeground()和方法stopForeground()是从Android2.0 (API Level 5)引入的.为了在早期版本是于前台运行你的service,你必须使用以前的那个setForeground()方法—见startForeground()API文档查看如何提供与旧版本的兼容性.



管理Service的生命期

  一个service的生命期比一个activity要简单得多.然而,你依然需要密切关注你的service是如何被创建又是如何被销毁的,因为一个service可以运行于后台而用户看不到它.


 service的生命期—从它被创建到它被销毁—有两条路可走:

  • 一个"启动的"service

      在其它组件调用startService()时创建.然后service就长期运行并且必须调用stopSelf()自己停止自己.另一个组件也可以调用stopService()来停止它.当service停止后,系统就销毁它.

  • 一个绑定的service

      当另一个组件(一个客户端)调用bindService()时创建.然后客户端通过一个IBinder接口与service通信.客户端可以调用unbindService()停止通信.多个客户端可以绑定到同一个service并且当所有的客户端都解除绑定后,系统就销毁掉这个service(service不需停止自己.)


  这两条路并不是完全分离的.也就是,你是可以绑定到用startService()启动的service的.例如,一个后台音乐service在通过传入指明要播放的音乐的intent来调用startService()后启动.之后,当用户想对播放器进行一些操作或要获取当前歌曲的信息时,一个activity可以通过调用bindService()绑定到service.在此情况下,stopService()stopSelf()不会真正的停止service,除非所有的客户端都取消绑定了.


实现生命期回调方法


  就像activityservice也具有生命期回调方法,用它们你可以监视service的状态的变化并且在合适的时机做一些工作.下面的框架代码演示了每个生命期方法的实现:

<span style="font-size:14px;">public class ExampleService extends Service {
    int mStartMode;       // 表明在service被杀后的行为
    IBinder mBinder;      // 客户端绑定到的接口
    boolean mAllowRebind; // 表明onRebind是否应被使用

    @Override
    public void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // service 正在启动,在调用startService()期间被调用
        return mStartMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // 一个客户端通过bindService()绑定到这个service
        return mBinder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // 所有的客户端使用unbindService()解除了绑定 
        return mAllowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // 一个客户端在调用onUnbind()之后,正使用bindService()绑定到service
    }
    @Override
    public void onDestroy() {
        // service不再被使用并将被销毁
    }
}
</span>
注:不像activity的生命期回调方法们,你不需要调用父类的相应实现.


图 2.service的生命期.左图显示了用startService()创建的service的生命期,右图显示了用bindService()创建的service的生命期.


通过实现这些方法们,你可以监视service生命期的两个嵌套循环:

  • service的一生介于调用onCreate()的时间和onDestroy()返回的时间.就像activityserviceonCreate()中做初始化工作并且onDestroy()中释放所有资源.例如,一个音乐播放service可以在onCreate()中创建音乐播放的线程,之后在onDestroy()中停止这个线程.

      onCreate()onDestroy()方法被所有的service调用,不管它们通过startService()还是bindService()创建.

  • service的活动生命期开始于onStartCommand()onBind()被调用时.每个方法各自处理传入的Intent

      如果service是"启动的",活动生命期就结束于整个生命期的结束时(即使onStartCommand()返回后,service依然处于活动状态).如果是一个绑定的service,它的活动生命期在onUnbind()返回后结束.



注:尽管一个"启动的"service在调用stopSelf()stopService()时结束,但并没有单独的回调对应这些停止方法(没有类似于onStop()的回调).所以,除非service被绑定到一个客户端,系统就会在停止时销毁service—onDestroy()是唯一收到的回调.



2演示了service的典型回调.尽管图示分开了通过startService()bindService()创建的service,但记住任何service,不管它是怎样启动的,都是可能允许绑定的.所以一个从onStartCommand()启动的service(客户端调用了startService())仍可以接收onBind()调用(当客户端调用bindService())



更多关于创建提供绑定的service的信息,请看后面的绑定Service详解.它会包含onRebind()回调方法的更多信息.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值