活动 —— 概述

文章译自:http://developer.android.com/intl/zh-CN/guide/components/activities.html


内容目录


  1. 创建活动
    1. 实现用户界面
    2. 在清单文件中声明活动
  2. 启动活动
    1. 为结果启动活动
  3. 关闭活动
  4. 管理活动的生命周期
    1. 实现生命周期回调
    2. 保存活动状态
    3. 处理配置改变
    4. 协调活动


快速浏览

  • 在应用中,一个活动为用户提供一个单独屏幕的用户界面
  • 活动可以移至到后台,然后随它们状态被恢复



活动


活动(Activity)是应用程序的一个组件,它提供一个用户可以与之交互屏幕,进而执行某些操作,譬如拨号,取图,发送邮件,或者浏览等。每个活动被给予一个窗口,在该窗口中绘制它的用户界面。窗口通常填充屏幕,但是它也可能比屏幕小并浮于其他窗口之上。


一个应用通常由多个彼此松散绑定的活动组成。通常情况下,一个活动在应用中被指定为"主"活动,当用户第一次启动该应用时,该活动首先呈现给用户。此外,每个活动都可以启动其他的活动以执行不同的动作。每当新活动启动时,上一个活动就被停止,但是系统会把该活动保存在堆栈中(即,"后退堆栈")。当新活动开始时,它被推入到后退堆栈中并获得焦点。后退堆栈遵守基本的"后进,先出"的堆栈机制,所以,当用户在当前活动里执行完操作后,接着按下Back按钮,此时它从堆栈中弹出(并销毁),然后恢复先前的活动。更多关于后退堆栈的讨论在任务和后退堆栈文档中。


当由于新活动的开始导致先前的活动停止时,系统通过活动生命周期回调方法通知它此类状态变化。有几个回调方法,活动可能会收到它们,这要基于活动的状态变化 — 系统是否正在创建它,正在停止它,正在恢复它,或正在销毁它 — 每个回调都为你提供了机会来执行与那个状态变化相适应的特定操作。例如,当活动停止时,活动应该是否任何大的对象,譬如网络或数据库连接等。当活动恢复时,你可以再次获得需要的资源并恢复被中断的操作。这些状态的转换是活动生命周期的全部。


文章的其余内容将讨论如何构建和使用活动的基本知识,包括一个完整的关于活动生命周期是如何工作的讨论,这样你就可以适当地管理各种活动状态间的转换。



1. 创建活动


为了创建活动,你必须创建一个Activity的子类(或者是它的一个现有子类)。在你的子类中,需要实现几个回调方法,当活动在它的各种生命周期状态间转化时,譬如当活动正在被创建,正在被停止,正在被恢复或正被销毁时由系统调用这些方法。两个重要的回调方法是:

onCreate()
必须实现这个方法。当创建活动时,由系统调用它。在你的实现内部,应该初始化最基本的活动组件。最重要的是,它是你必须调用setContentView()来为活动的用户界面定义布局的地方。
onPause()
系统调用该方法作为用户正在离开活动的第一个暗示(尽管它并不总是意味着该活动正被销毁)。这通常是你应该提交任何应当跨越当前会话被持久保存的改变的地方(因为用户可能不会回来了)。

还有几个其他的生命周期回调方法,你可以使用它们来为用户在活动和处理可以导致活动被停止及甚至是销毁的意外中断之间提供流畅的用户体验。稍后在关于管理活动生命周期的章节中讨论所有的生命周期回调方法。



1.1 实现用户界面


活动的用户界面是由层级结构的视图提供的 —— 这些视图对象是由View类继承而来的。每个视图控制着一块活动窗口内特定的矩形空间,并可以响应用户交互。例如视图可能是一个button,当用户触碰它时,它会执行一个动作。

 

Android提供了一些现成的视图,你可以使用它们来设计和组织布局。"Widgets"是为屏幕提供可视化的(及可交互的)元素的视图,比如一个按钮,文本输入框,复选框或只是一个图片。"Layouts"是从ViewGroup继承而来的视图,它们为它的子视图提供了唯一的布局模式,比如线性布局,网格布局或相对布局。你也可以子类化ViewViewGroup类(或者它们现有的子类)来创建你自己的部件和布局,并把它们应用到你的活动布局。


使用视图定义布局的最常见的方式是通过一个XML布局文件,它保存在应用程序的资源里。通过该方式,你可以独立于定义了活动行为的源代码来维护用户界面的设计。可以通过向setContentView()传递布局的资源ID来将该布局设置为活动的UI。然而,你也可以在活动代码里创建新的视图Views),以及通过向ViewGroup插入新的视图来构建一个视图层级结构,然后再通过把根ViewGroup传递给setContentView()来使用这个布局。


更多关于创建用户界面的内容,请参阅用户界面文档。


1.2 在清单文件中声明活动


为使活动可被系统访问,你必须在清单文件中声明它。为了声明活动,打开清单文件并添加<activity>元素作为<application>元素的子元素。 比如:

<manifest ... >
  <application ... >
      <activity android:name=".ExampleActivity" />
      ...
  </application ... >
  ...
</manifest >

可以在这个元素里包含其他几个属性来定义活动特征,比如活动的标签,活动的图标,或用来设计活动UI的主题。android:name属性是唯一必备的属性 —— 它指定了活动的类名。一旦发布了应用,就不应该修改这个名字,因为如果你这样做了,可能会破坏某些功能,比如应用程序的快捷方式(阅读博客帖子,不能修改的东西)。


更多关于在清单文件中声明活动的信息,请参阅<activity>元素参考。


使用意图过滤器

<activity> 元素也可以指定各种意图过滤器 — 通过使用<intent-filter>元素 — 来声明其他应用组件如何可以激活它


当使用Android SDK工具创建新的应用程序时,工具为你自动创建的短小活动中包含了一个这样的意图过滤器:它声明该活动响应"main"动作并且应该被放置"launcher"类别中。该意图过滤器看起像这样:

<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<action>元素指定了这是应用程序的"主"入口点。<category>元素则指定了该活动应该被列在系统的应用程序启动器中(以允许用户启动这个活动)。


如果你打算让自己的应用是独立的,并不允许其他应用来激活它的活动,那么你就不必包含任何意图过滤器。只能有一个活动拥有"main"动作和"launcher"类别,如上所示。不想对其他应用程序可用的活动不应该具有意图过滤器,不过你可以通过使用显式意图来启动它们。


但是,如果想让你的活动响应来自其他应用程序(及你自己的)的隐式意图,那么必须为你的活动定义额外意图过滤器。对于你想要响应的每个类型的意图,你的活动必须包含含有<action>元素的<intent-filter>,同时,它可选择性地包含<category>元素和/或<data>元素。这些元素说明了你的活动可以响应意图类型。


更多关于活动如何响应意图的内容,请参阅意图和意图过滤器文档。




2. 启动活动


你可以通过向调用的startActivity()传递一个描述想要启动的活动的意图来启动其他的活动。意图要么指定了你想要开始的确切活动,要么描述了你想要执行的动作类型(然后系统为你选择合适的活动,即便是它来自不同应用程序)。意图还可以携带供被启动的活动所使用的少量数据。


当在你自己的应用内工作时,你可能常常需要简单地启动一个已知的活动。为做到这一点,你可以通过创建一个使用类名来显式地定义你打算启动的活动的意图。下面是一个活动如何启动名为SignInActivity的其他活动的例子:

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

不过,你的应用也可能打算执行若干动作,譬如使用自身活动的数据来发送邮件,文本消息,或状态更新。这种情况下,你的应用可能不会让自己的活动来执行此类动作,所以你可以转而利用设备上的其他应用提供的活动,让它们为你执行那些动作。这就是意图真正有价值的地方 —— 你可以创建一个描述你想要执行的动作的意图,然后系统从其他应用启动恰当的活动。如果有多个可以处理该意图的活动,那么用户可以选择一个来使用。比如,如果你想让用户发送电子邮件,你可以创建如下意图:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

添加给意图的EXTRA_EMAIL额外数据是一个邮件应被发往的邮件地址的字符串数组。当电子邮件应用程序响应这个意图时,它读取由额外数据提供的字符串数组,并把它们放置在邮件组成形式的"to"域中。在这种情况下,启动电子邮件应用程序的活动,并当用户完成发送时,恢复自己的活动。


2.1 为结果启动活动

有时,你可能想从你启动的活动中收到结果。在这种情况下,通过调用startActivityForResult()来启动活动(替代startActivity())。为了从随后的活动中收到结果,需要实现onActivityResult() 回调方法。当随后的活动完成时,它通过意图向你的onActivityResult()方法返回结果。


例如,你可能想让用户选取一个他们的联系人,这样,你的活动就可以用那条联系人信息做些什么了。下面是如何创建意图并处理结果的例子:


private void pickContact() {
    // Create an intent to "pick" a contact, as defined by the content provider URI
    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
    startActivityForResult(intent, PICK_CONTACT_REQUEST);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
        // Perform a query to the contact's content provider for the contact's name
        Cursor cursor = getContentResolver().query(data.getData(),
        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
        if (cursor.moveToFirst()) { // True if the cursor is not empty
            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
            String name = cursor.getString(columnIndex);
            // Do something with the selected contact's name...
        }
    }
}

这个示例展示了为处理活动结果需在onActivityResult()方法内应该使用的基本逻辑。第一个条件是检查请求是否成功(如果是,resultCode将是RESULT_OK),以及这个结果正在响应的请求是否是已知的(在这种情况下,requestCode匹配传递给startActivityForResult()的第二个参数)。从那里,代码通过查询在意图内返回的数据来处理活动结果(data参数)。


ContentResolver对一个返回允许读取查询数据的Cursor的内容提供者进行查询时会发生什么呢,请参阅内容提供者文档。


更多关于使用意图的信息,请参阅意图和意图过滤器文档。




3.  关闭活动


可以通过调用活动的finish()方法将其关闭。还可以通过调用finishActivity()来关闭一个单独的先前启动的活动(该活动是通过startActivityForResult(Intent, int)方法被启动的)。


注意:在绝大多数情况下,不应该显式地使用这些方法来结束活动。正如接下来关于活动生命周期章节内的讨论,Android系统为你管理活动的生命,所以你不必亲自来结束自己的活动。调用这些方法可能对期待的用户体验产生不利地影响,因此,只有在你完全不想让用户回到该活动的实例时才应该使用它们。



4. 管理活动的生命周期



通过实现回调方法来管理活动的生命周期是开发一个健壮和灵活的应用程序的关键。一个活动的生命周期直接受与之相关联的其他活动的影响,以及它的任务及后退堆栈。


实际上,活动可以存在于三个状态:


重新恢复 —— Resumed
该活动位于屏幕的前面,并拥有用户焦点。(此状态有时也称之为"运行"。)

已暂停 —— Paused
另外的活动位于前台,并拥有焦点,但是这个活动依然可见。也就是说,另一个活动在这个活动之上显示,并且那个活动是部分透明的或者没有覆盖整个屏幕。已暂停的活动是完全活着的(Activity对象被保存在内存里,它维持着全部的状态和成员信息,并且依然附属于窗口管理器), 但在内存非常低的情况下可以被系统杀死。

停止 —— Stopped

该活动被另外的活动完全遮盖(该活动现在位于"后台")。同样,已停止的活动依然是活着的(Activity对象被保存在内存里,它维持着所有的状态及成员信息,但是不再依附于窗口管理器)。但是,该活动不再对用户可见,并且当其他地方需要内存时可被系统杀死。


如果一个活动被暂停或被停止,系统可以通过要求它结束(调用它的finish() 方法),或简单地杀死它的进程来将其从内寸中删除。当活动再次被打开时(在其被杀死或被结束之后),它必须

被彻底地重建。



4.1 实现生命周期回调


当活动在上述的不同状态间来回切换时,它是通过各种回调函数获得通知的。所有的回调函数都是你可以重写的以在活动状态发生改变时来执行恰当操作的钩子。下面的骨架活动包括了最基本的生命周期中的每个方法:

public class ExampleActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 活动正被创建.
    }
    @Override
    protected void onStart() {
        super.onStart();
        // 活动即将变为可见.
    }
    @Override
    protected void onResume() {
        super.onResume();
        // 该活动已经可见(现在是 "resumed").
    }
    @Override
    protected void onPause() {
        super.onPause();
        //另外的活动获得焦点(这个活动即将为"paused").
    }
    @Override
    protected void onStop() {
        super.onStop();
        //该活动不在可见 (现在是 "stopped")
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 该活动即将被销毁.
    }
}


注意:这些生命周期方法的实现在做任何操作之前通常必须调用基类的实现,如上例所示。


总的来说,这些方法定义了活动的整个生命周期。通过实现这些方法,可以控制生命周期内的三个嵌套循环:

  • 活动的整个生命周期发生在onCreate()调用和onDestroy()调用之间。活动应该在onCreate()内执行"全局"状态设置(比如定义布局等),然后在onDestroy()内释放所有遗留的资源。例如,如果你的活动有一个运行在后台来从网络上下载数据的线程,则该活动可能在onCreate() 里创建那个线程,然后在onDestroy()内停止该线程。
  • 活动的可见生命周期发生在onStart()调用和onStop()调用之间。在此期间,用户可在屏幕上看到该活动,并且能够与之交互。 例如,当一个新活动启动并且先前的那个不再可见时调用onStop()。在这两个方法之间,你可以维护那些借此把活动展现给用户的资源。例如,你可以在onStart()里注册一个BroadcastReceiver来控制影响你的用户界面的变动,然后当用户不再看到你正展示的东西时,在onStop() 内注销BroadcastReceiver。 在活动的整个生命周期期间,随着活动在对用户可见和隐藏之间进行切换,系统可能会多次调用onStart()onStop()

  • 活动的前台生命周期 发生在onResume()调用和onPause()调用之间。在此期间, 活动位于屏幕上所有活动的前面,并拥有用户输入焦点。活动可以在前景内外频繁地转换 —— 例如,当设备休眠或出现对话框时,则onPause()方法被调用。由于此状态经常发生转换,因此为避免使用户等待的缓慢转换,这两个方法内的代码应该是相当轻量级的。


图 - 1 说明了这些循环及活动在各状态间采用的路径。矩形代表活动在各状态间转换时可以实现来执行操作的回调方法。



图 - 1 活动生命周期



相同的生命周期方法列出在表 - 1列,该表描述了每个回调方法的更多细节(包括在回调完成后系统是否可以杀死活动),同时在活动的完整生命周期内对它们进行了定位。


表 - 1. 活动生命周期的回调方法总结


方法

描述

之后是否可杀?

接下来

onCreate()

活动首次被创建时调用。这是应该执行正常的静态设置的地方 ,比如创建视图,把数据绑定到列表上,等等。该方法接受一个包含活动先前状态的Bundle对象,如果其状态曾被捕获过(稍后参阅保存活动状态)。onStart()经常紧随其后。

No

onStart()

    

onRestart()

在活动已被停止后,恰先于它被再次开始之前调用。onStart()经常紧随其后。

No

onStart()

onStart()

刚好在活动对用户可见之前调用。如果活动来到前台,则onResume()紧随其后,或者如果活动被隐藏了,则onStop()紧随其后。

No

onResume()

onStop()

    

onResume()

恰在活动开始与用户交互之前调用。此时,活动位于活动堆栈的顶部,并持接受用户属于。onPause()通常紧随其后。

No

onPause()

onPause()

当系统即将恢复其他活动时调用。该方法通常被用来把未保存的改变提交为持久化的数据,停止动画及其他可能消耗CPU的操作等。无论它做什么都应该快速完成,因为下一个活动将直到该方法返回后才被恢复。如果活动返回到前台,则onResume()紧随其后,或者如果它对用户变得不可见了,则onStop()紧随其后。

Yes

onResume()

onStop()

onStop()

当活动不再对用户可见时调用。由于它正被销毁,或由于其他活动(现有的活动或是新的活动)被恢复并且正在覆盖它时,都可以发生此调用。

如果活动正在返回至前台以同用户交互,则onRestart()紧随其他,或者如果该活动将要销毁时,则onDestroy()紧随其后。.

Yes

onRestart()

onDestroy()

onDestroy()

活动被销毁前调用。这是活动将要接收的最终调用。要么因为活动正在结束(某处调用了它的finish()),或是由于系统临时地销毁活动的实例以节省内存,都可以调用它。可以通过isFinishing()方法来区分这两种情况。

Yes

 

 

标记"之后可是否可杀?"的列表明系统在方法返回后的任何时候是否可以杀死该活动的宿主进程,而不用执行该活动代码的其他行。三个方法被标记了"Yes":(onPause(),onStop(), 和onDestroy())。由于onPause()是这三个方法中的第一个,所以一旦活动被创建了,则onPause() 是最近的保证在进程被杀死之前被调用的方法 —— 如果系统在紧急时刻必须恢复内存,那么 onStop()onDestroy()可能不会被调用到。因此,你应该使用onPause()来把重要的持久性数据(比如用户的编辑)写入存储器中。然而,你应该选择什么信息必须在onPause()期间被保存,因为该方法内任何阻塞的过程都会阻止转换到下个活动,并且降低用户体验。


在"之后可是否可杀?"列内被标记了"No"的方法保护着活动的宿主进程自它们(指标识了"No"的方法)被调用那刻起不会被杀死。因此,活动自onPause() 返回时到onResume()被调用时(应该是被调用之前的任意时刻)是可被杀死的。  直到onPause()被再次调用并返回时活动才会被再次杀死。


注意:在表1里,不是技术上被定义为"可杀"活动依然可被系统杀死 —— 但是只发生在当没有其它资源时的极端情况下。进程和线程文档内有更多关于活动何时可被杀死的讨论。



4.2 保存活动状态


管理活动生命周期的介绍中简单地提到了活动何时被暂停或停止,及何时保存活动的状态。这是事实,因为当活动被暂停或停止时,Activity对象依然驻留在内存中 —— 所有关于它的成员及当前状态的信息依然存在。因此,用户在活动内做出的任何改变都会被保留,目的是当活动返回至前台时(当它"恢复"时),那些改变依然存在。


然而,当系统为了恢复内存而销毁活动时,Activity对象也被销毁了,所以系统不能简单地伴随以其完整的状态简单地恢复它。相反,如果用户导航回它,系统必须重新创建Activity对象。但是,用户并没有察觉系统销毁了活动并重新创建了它,因此,用户很可能希望活动与其原来完全一样。这种情况下,通过实现一个额外的允许你保存关于活动状态信息的回调方法:onSaveInstanceState(),你可以确保关于活动状态的重要信息被保留。


系统在使活动易受到销毁之前调用onSaveInstanceState()。系统向该方法传递一个Bundle对象,你可以使用诸如putString()putInt()等方法把活动的状态信息作为名称-值对保存在该对象里。于是,如果系统杀死了你的应用进程,并且用户导航回至你的活动,系统则重新创建该活动并把Bundle一同传递给onCreate()onRestoreInstanceState()。通过使用这些方法中的任何一个,你可以从Bundle中提取你保存的状态并恢复该活动的状态。如果没有状态信息来恢复,那么传递给你的Bundle则为空(当活动初次被创建时就是这种情况)。

  

图 2. 活动通过两种方式以其完整的状态返回到用户焦点: 要么是活动被销毁,然后又被重新创建,并且活动必须恢复先前保存的状态,或者是活动被停止,然后恢复,同时活动状态保持不变。


注意:  在你的活动被销毁之前,并不能保证onSaveInstanceState() 被调用到, 因为在很多情况下不需要保存状态(比如当用户通过使用Back按钮离开活动时,因为用户正在显式地关闭活动)。如果系统调用onSaveInstanceState(),它在onStop() 之前并且很可能是在onPause()调用它。


然而,即使你什么也不做,并且不实现onSaveInstanceState(), 一些活动状态通过Activity类的默认的onSaveInstanceState() 实现得以恢复(原文应该为"reserved")。具体来说,默认的实现对布局内每个View调用对应的onSaveInstanceState()方法,它允许每个视图来提供有关其自身的应该被保存的信息。几乎每一个在Android框架里的应用小部件都适当地实现了这个方法, 因此使得对UI的任何可见的改变都被自动地保存,并在活动被重新创建时自动恢复。例如,EditText 小部件保存任何由用户键入的文本,以及CheckBox 小部件保存其状态,无论它是否被中。唯一需要由你做的是为每个你想要其保存其状态的小部件提供一个唯一的ID(android:id属性)。如果小组件没有ID,那么系统将不能保存它的状态。


尽管默认的onSaveInstanceState() 实现保存了有关于活动UI的有用信息,你还是可能需要重写它以保存额外的信息。例如,你可能需要保存在活动生命周期期间发生变化的成员值(可能与在UI上恢复的值有关,但是默认情况下,持有那些UI值的成员不会被恢复)。


提示:你也可以通过设置android:saveEnabled属性为"false"或通过调用setSaveEnabled()方法来显式地阻止布局内的视图保存其状态。通常,你不应用禁止保存,但如果你想有区别地恢复活动UI状态的话,你可以禁止的。


注意:因为onSaveInstanceState() 方法不能保证被调用到,因此你应该只用它记录短暂的活动状态(UI的状态) —— 而你绝不应该使用它来保存持久性数据。相反,当用户离开活动时,你应该使用onPause()来保存持久性数据(比如应当被保存到数据库的数据)。


一个测试你的应用保存其状态的能力的好办法是简单地旋转设备,以便使设备屏幕方向改变。当屏幕方向改变时,系统销毁并重建活动以应用可能用于新屏幕配置的替代资源。仅就这点而言,当你的活动被重建时,它完全地恢复了其状态,这是十分重要的,因为用户在使用应用时会经常旋转屏幕。


4.3  处理配置改变


一些设备配置可以在运行期间发生改变(比如屏幕方向,键盘可用性及语言)。当发生此类改变时,Android重新创建正在运行的活动(系统调用onDestroy(),接着立刻调用onCreate())。此行为被设计用来帮助你的应用适应于新的配置,通过以你所提供的可替代资源(比如适用于不同屏幕方向和尺寸的不同布局)重新加载你的应用。


如果你正确地设计了你的活动来处理因屏幕方向改变而引起的重启以及像上面描述那样恢复你的活动状态,那么你的应用将更适应于活动生命周期中的其他的意想不到的事件。


处理此类重启的最好办法是使用onSaveInstanceState()和onRestoreInstanceState() (或onCreate())保存和恢复活动的状态,正如上一章节所讨论的。


关于发生在运行时的配置改变的更多信息及如何处理它们,请参阅处理运行时改变向导。


4.4  协调活动


当一个活动开始另一个时,它们两个将经历生命周期转变。第一个活动暂停并停止(不过,如果它在后台依然可见,它将不会被停止),然后另一个活动被创建。在这两个活动共享保存在硬盘或其他地方的数据的情况下,对于理解第一个活动在第二个被创建前并它没有完全被停止是重要的。相反,开始第二个活动过程与停止第一个活动的过程是有重叠的。


生命周期回调顺序被明确地定义了,特别是当两个活动在同一进程并且一个活动正在开始另一个活动时。下面是发生在当活动A开始活动B时的操作顺序:

  1. 活动A的onPause()方法执行。
  2. 活动B的onCreate(), onStart(),和onResume()方法按顺序执行。 (活动B现在已经拥有用户焦点)
  3. 接着,如果活动A在屏幕上不再可见,它的 onStop()方法执行。


这种可预见的生命周期回调顺序允许你管理从一个活动到另一个活动的信息转变。例如,当第一个活动停止时,如果你必须向数据库写入以便让接下来的活动能够读它,那么你应该在onPause()期间向数据库写入,而不是在onStop()期间。


2012年9月18日,毕











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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值