Activity 总结

       Activity是Android程序与用户交互的窗口,在我看来,从这个层面的视角来看,Android的Activity特像网站的页面。在其上可以布置按钮,文本框等各种控件,简单来说就是Android的UI部分,在手机或平板电脑的屏幕上你看到的界面就是她喽!

       Activity,在四大组件中,无疑是最复杂的,从视觉效果来看,一个Activity占据当前的窗口,响应所有窗口事件,具备有控件,菜单等界面元素。从内部逻辑来看,Activity需要为了保持各个界面状态,需要做很多持久化的事情,还需要妥善管理生命周期,和一些转跳逻辑。对于开发者而言,就需要派生一个Activity的子类,然后埋头苦干上述事情。

        Activity是用户和应用程序交互的窗口,一个activity相当于我们实际中的一个网页,当打开一个屏幕时,之前的那一个屏幕会被置为暂停状态,并且压入历史堆栈中,用户可以通过回退操作返回到以前打开过的屏幕。activity的生命周期:即“产生、运行、销毁”,但是这其中会调用许多方法onCreate(创建) 、onStart(激活) 、onResume(恢复) 、onPause(暂停) 、onStop(停止) 、onDestroy(销毁) 、onRestart(重启)。


函数 
是否
可终止 
说明 
onCreate() 
否 
Activity启动后第一个被调用的函数,常用来进行Activity的初始化,例如创建View、绑定数据或恢复信息等。 
onStart() 
否 
当Activity显示在屏幕上时,该函数被调用。 
onRestart() 
否 
当Activity从停止状态进入活动状态前,调用该函数。 
onResume() 
否 
当Activity能够与用户交互,接受用户输入时,该函数被调用。此时的Activity位于Activity栈的栈顶。 
onPause() 
是 
当Activity进入暂停状态时,该函数被调用。一般用来保存持久的数据或释放占用的资源。 
onStop() 
是 
当Activity进入停止状态时,该函数被调用。 
onDestroy() 
是 
在Activity被终止前,即进入非活动状态前,该函数被调用。

这七个方法定义了Activity的完整生命周期。实现这些方法可以帮助我们监视其中的三个嵌套生命周期循环:

       Activity的完整生命周期自第一次调用onCreate()开始,直至调用onDestroy()为止。Activity在onCreate()中设置所有“局”状态以完成初始化,而在onDestroy()中释放所有系统资源。例如,如果Activity有一个线程在后台运行从网络上下载数据,它会在onCreate()创建线程,而在 onDestroy()销毁线程。

       Activity的可视生命周期自onStart()调用开始直到相应的onStop()调用结束。在此期间,用户可以在屏幕上看到Activity,管它也许并不是位于前台或者也不与用户进行交互。在这两个方法之间,我们可以保留用来向用户显示这个Activity所需源。例如,当用户不再看见我们显示的内容时,我们可以在onStart()中注册一个BroadcastReceiver来监控会影响UI的话,而在onStop()中来注消。onStart() 和 onStop() 方法可以随着应用程序是否为用户可见而被多次调用。

        Activity的前台生命周期自onResume()调用起,至相应的onPause()调用为止。在此期间,Activity位于前台最上面并与用进行交互。Activity会经常在暂停和恢复之间进行状态转换——例如:当设备转入休眠状态或者有新的Activity启动时,将调onPause() 方法。当Activity获得结果或者接收到新的Intent时会调用onResume() 方法。


Activity的启动有三种,

      1、onCreate()
      第一次启动这个Activity时候一定是会先从这里启动Activity的。如果一些常量需要初始化,比如话机读取本地配置,获得线路类型、地理位置之       类的信息都要在这里做

       2、onRestart()
      从当前Activity  A跳转到另外一个Activity  B时候,A如果不手动处理的话,是暂时被压到Activity堆栈里面了。如果再次从B或者另
      外的Activity跳回A,此时A就会从onRestart()函数启动

      3、onNewIntent(Intent intent)
      从这个函数的启动不大常见,如果一个TabLayout的content内容填充的都是activity的话,例如系统的联系人程序,点击联系人或者拨号时候,       出现的是一个TabActivity,这时候其实拨号程序、联系人程序、通话记录、收藏四个Activity都调用了onCreate函数, 此时,如果点击的是联系       人程序,打开的TabActivity默认的界面就是联系人列表的界面,此时如果点击拨号的Tab时候,拨号程序的启动就是从onNewIntent(Intent             intent)这个函数再次启动的。到现在为止我也只是发现了此一种情况是从这个函数再次启动一个activity的,要是知道别的情况也是从这个函数       启动的话麻烦告知下。

为什么要研究Activity的启动呢?有时候我们设置了一些全局变量,比如系统的通话记录程序,每次启动它的时候都要刷新下,不然刚刚拨打过电话的记录就不会显示,类似的还有如果我们有一些全局需要调用的变量是从一个本地配置文件读取的,但是当Activity压栈时候,本地配置或者全局变量发生变化呢?如果不重新读取下,它的值就不会改变,程序就会出错的。


Activity其实是继承了ApplicationContext这个类,我们可以重写以下方法,如下代码:

public class Activity extends ApplicationContext {

protected void onCreate(Bundle savedInstanceState);

protected void onStart();

protected void onRestart();

protected void onResume();

protected void onPause();

protected void onStop();

protected void onDestroy();

}


简单的写了一个Demo,不明白Activity周期的朋友们,可以亲手实践一下,大家按照我的步骤来。

第一步:新建一个Android工程,我这里命名为ActivityDemo.

第二步:修改ActivityDemo.java(我这里重新写了以上的七种方法,主要用Log打印),代码如下:

package com.tutor.activitydemo;

public class ActivityDemo extends Activity {

private static final String TAG = "ActivityDemo";

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

Log.e(TAG, "start onCreate~~~");

}

protected void onStart() {

super.onStart();

Log.e(TAG, "start onStart~~~");

}

protected void onRestart() {

super.onRestart();

Log.e(TAG, "start onRestart~~~");

}

protected void onResume() {

super.onResume();

Log.e(TAG, "start onResume~~~");

}

protected void onPause() {

super.onPause();

Log.e(TAG, "start onPause~~~");

}

protected void onStop() {

super.onStop();

Log.e(TAG, "start onStop~~~");

}

protected void onDestroy() {

super.onDestroy();

Log.e(TAG, "start onDestroy~~~");

}

}

package com.tutor.activitydemo;

public class ActivityDemo extends Activity {

private static final String TAG = "ActivityDemo";

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

Log.e(TAG, "start onCreate~~~");

}

protected void onStart() {

super.onStart();

Log.e(TAG, "start onStart~~~");

}

protected void onRestart() {

super.onRestart();

Log.e(TAG, "start onRestart~~~");

}

protected void onResume() {

super.onResume();

Log.e(TAG, "start onResume~~~");

}

protected void onPause() {

super.onPause();

Log.e(TAG, "start onPause~~~");

}

protected void onStop() {

super.onStop();

Log.e(TAG, "start onStop~~~");

}

protected void onDestroy() {

super.onDestroy();

Log.e(TAG, "start onDestroy~~~");

}

}

第三步:运行上述工程,效果图如下(没什么特别的):

核心在Logcat视窗里,如果你还不会用Logcat你可以看一下我的这篇文章 Log图文详解(Log.v,Log.d,Log.i,Log.w,Log.e) ,我们打开应用时先后执行了onCreate()->onStart()->onResume三个方法,看一下LogCat视窗如下:

BACK键:

当我们按BACK键时,我们这个应用程序将结束,这时候我们将先后调用onPause()->onStop()->onDestory()三个方法,如下图所示:

HOME键:

当我们打开应用程序时,比如浏览器,我正在浏览NBA新闻,看到一半时,我突然想听歌,这时候我们会选择按HOME键,然后去打开音乐应用程序,而当我们按HOME的时候,Activity先后执行了onPause()->onStop()这两个方法,这时候应用程序并没有销毁。如下图所示:

而当我们再次启动ActivityDemo应用程序时,则先后分别执行了onRestart()->onStart()->onResume()三个方法,

这里我们会引出一个问题,当我们按HOME键,然后再进入ActivityDemo应用时,我们的应用的状态应该是和按HOME键之前的状态是一样的,同样为了方便理解,在这里我将ActivityDemo的代码作一些修改,就是增加一个EditText。

第四步:修改main.xml布局文件(增加了一个EditText),代码如下:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

<TextView

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/hello"

/>

<EditText

android:id="@+id/editText"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

/>

</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

<TextView

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="@string/hello"

/>

<EditText

android:id="@+id/editText"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

/>

</LinearLayout>

第五步:然后其他不变,运行ActivityDemo程序,在EditText里输入如"Frankie"字符串

这时候,大家可以按一下HOME键,然后再次启动ActivityDemo应用程序,这时候EditText里并没有我们输入的"Frankie"字样,如下图:

这显然不能称得一个合格的应用程序,所以我们需要在Activity几个方法里自己实现,如下第六步所示:

第六步修改ActivityDemo.java代码如下:

package com.tutor.activitydemo;

public class ActivityDemo extends Activity {

private static final String TAG = "ActivityDemo";

private EditText mEditText;

//定义一个String 类型用来存取我们EditText输入的值

private String mString;

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

mEditText = (EditText)findViewById(R.id.editText);

Log.e(TAG, "start onCreate~~~");

}

protected void onStart() {

super.onStart();

Log.e(TAG, "start onStart~~~");

}

//当按HOME键时,然后再次启动应用时,我们要恢复先前状态

protected void onRestart() {

super.onRestart();

mEditText.setText(mString);

Log.e(TAG, "start onRestart~~~");

}

protected void onResume() {

super.onResume();

Log.e(TAG, "start onResume~~~");

}

//当我们按HOME键时,我在onPause方法里,将输入的值赋给mString

protected void onPause() {

super.onPause();

mString = mEditText.getText().toString();

Log.e(TAG, "start onPause~~~");

}

protected void onStop() {

super.onStop();

Log.e(TAG, "start onStop~~~");

}

protected void onDestroy() {

super.onDestroy();

Log.e(TAG, "start onDestroy~~~");

}

}

package com.tutor.activitydemo;

public class ActivityDemo extends Activity {

private static final String TAG = "ActivityDemo";

private EditText mEditText;

//定义一个String 类型用来存取我们EditText输入的值

private String mString;

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

mEditText = (EditText)findViewById(R.id.editText);

Log.e(TAG, "start onCreate~~~");

}

protected void onStart() {

super.onStart();

Log.e(TAG, "start onStart~~~");

}

//当按HOME键时,然后再次启动应用时,我们要恢复先前状态

protected void onRestart() {

super.onRestart();

mEditText.setText(mString);

Log.e(TAG, "start onRestart~~~");

}

protected void onResume() {

super.onResume();

Log.e(TAG, "start onResume~~~");

}

//当我们按HOME键时,我在onPause方法里,将输入的值赋给mString

protected void onPause() {

super.onPause();

mString = mEditText.getText().toString();

Log.e(TAG, "start onPause~~~");

}

protected void onStop() {

super.onStop();

Log.e(TAG, "start onStop~~~");

}

protected void onDestroy() {

super.onDestroy();

Log.e(TAG, "start onDestroy~~~");

}

}

第七步:重新运行ActivityDemo程序,重复第五步操作,当我们按HOME键时,再次启动应用程序时,EditText里有上次输入的"Frankie"字样。






Activity生命周期的详细介绍:

1. void onCreate(Bundle savedInstanceState)

当Activity被第首次加载时执行。我们新启动一个程序的时候其主窗体的onCreate事件就会被执行。如果Activity被销毁后(onDestroy后),再重新加载进Task时,其onCreate事件也会被重新执行。注意这里的参数 savedInstanceState(Bundle类型是一个键值对集合,大家可以看成是.Net中的Dictionary)是一个很有用的设计,由于前面已经说到的手机应用的特殊性,一个Activity很可能被强制交换到后台(交换到后台就是指该窗体不再对用户可见,但实际上又还是存在于某个Task中的,比如一个新的Activity压入了当前的Task从而“遮盖”住了当前的 Activity,或者用户按了Home键回到桌面,又或者其他重要事件发生导致新的Activity出现在当前Activity之上,比如来电界面),而如果此后用户在一段时间内没有重新查看该窗体(Android通过长按Home键可以选择最近运行的6个程序,或者用户直接再次点击程序的运行图标,如果窗体所在的Task和进程没有被系统销毁,则不用重新加载Process, Task和Task中的Activity,直接重新显示Task顶部的Activity,这就称之为重新查看某个程序的窗体),该窗体连同其所在的Task和Process则可能已经被系统自动销毁了,此时如果再次查看该窗体,则要重新执行 onCreate事件初始化窗体。而这个时候我们可能希望用户继续上次打开该窗体时的操作状态进行操作,而不是一切从头开始。例如用户在编辑短信时突然来电,接完电话后用户又去做了一些其他的事情,比如保存来电号码到联系人,而没有立即回到短信编辑界面,导致了短信编辑界面被销毁,当用户重新进入短信程序时他可能希望继续上次的编辑。这种情况我们就可以覆写Activity的void onSaveInstanceState(Bundle outState)事件,通过向outState中写入一些我们需要在窗体销毁前保存的状态或信息,这样在窗体重新执行onCreate的时候,则会通过 savedInstanceState将之前保存的信息传递进来,此时我们就可以有选择的利用这些信息来初始化窗体,而不是一切从头开始。

2. void onStart()

onCreate事件之后执行。或者当前窗体被交换到后台后,在用户重新查看窗体前已经过去了一段时间,窗体已经执行了onStop事件,但是窗体和其所在进程并没有被销毁,用户再次重新查看窗体时会执行onRestart事件,之后会跳过onCreate事件,直接执行窗体的onStart事件。

3. void onResume()

onStart事件之后执行。或者当前窗体被交换到后台后,在用户重新查看窗体时,窗体还没有被销毁,也没有执行过onStop事件(窗体还继续存在于Task中),则会跳过窗体的onCreate和onStart事件,直接执行onResume事件。

4. void onPause()

窗体被交换到后台时执行。

5. void onStop()

onPause事件之后执行。如果一段时间内用户还没有重新查看该窗体,则该窗体的onStop事件将会被执行;或者用户直接按了Back键,将该窗体从当前Task中移除,也会执行该窗体的onStop事件。

6. void onRestart()

onStop事件执行后,如果窗体和其所在的进程没有被系统销毁,此时用户又重新查看该窗体,则会执行窗体的onRestart事件,onRestart事件后会跳过窗体的onCreate事件直接执行onStart事件。

7. void onDestroy()

Activity被销毁的时候执行。在窗体的onStop事件之后,如果没有再次查看该窗体,Activity则会被销毁。

最后用一个实际的例子来说明Activity的各个生命周期。假设有一个程序由2个Activity A和B组成,A是这个程序的启动界面。当用户启动程序时,Process和默认的Task分别被创建,接着A被压入到当前的Task中,依次执行了 onCreate, onStart, onResume事件被呈现给了用户;此时用户选择A中的某个功能开启界面B,界面B被压入当前Task遮盖住了A,A的onPause事件执行,B的 onCreate, onStart, onResume事件执行,呈现了界面B给用户;用户在界面B操作完成后,使用Back键回到界面A,界面B不再可见,界面B的onPause, onStop, onDestroy执行,A的onResume事件被执行,呈现界面A给用户。此时突然来电,界面A的onPause事件被执行,电话接听界面被呈现给用户,用户接听完电话后,又按了Home键回到桌面,打开另一个程序“联系人”,添加了联系人信息又做了一些其他的操作,此时界面A不再可见,其 onStop事件被执行,但并没有被销毁。此后用户重新从菜单中点击了我们的程序,由于A和其所在的进程和Task并没有被销毁,A的onRestart 和onStart事件被执行,接着A的onResume事件被执行,A又被呈现给了用户。用户这次使用完后,按Back键返回到桌面,A的 onPause, onStop被执行,随后A的onDestroy被执行,由于当前Task中已经没有任何Activity,A所在的Process的重要程度被降到很低,很快A所在的Process被系统结束。

进一步具体的了解

Activity生命周期 
       理解Activity的生命周期对应用程序开发来说是至关重要的,这样才能确保您的应用提供了一个很好的用户体验和妥善管理其资源。由于OPhone应用程序不控制自己的进程寿命,由OPhone Runtime管理每个应用程序进程,但是每个Activity的状态反过来会影响到OPhone Runtime是否将终止当前Activity和还是让它继续运行。
Actvity 堆栈  
      每个Actvity的状态由它所在Activity栈中的位置所决定,所有当前正在运行的Actvity将遵循照后进先出的原则。当一个新的 Activity启动,当前的Activity将移至堆栈的顶部,如果用户使用Back按钮,或在前台Activity被关闭,下一个Activity将被激活并且移至到堆栈的顶部。


Activity状态
  • Active状态:这时候Activity处于栈顶,且是可见的,有焦点的,能够接收用户输入前景Activity。OPhone Runtime将试图不惜一切代价保持它活着,甚至杀死其他Activity以确保它有它所需的资源。当另一个Activity变成Active时,当前的将变成Paused状态。
  • Paused状态:在某些情况下,你的Activity是可见的,但没有焦点,在这时候,Actvity处于Paused状态。例如,如果有一个透明或非全屏幕上的Activity在你的Actvity上面,你的 Activity将。当处于Paused状态时,该Actvity仍被认为是Active的,但是它不接受用户输入事件。在极端情况下,OPhone Runtime将杀死Paused Activity,以进一步回收资源。当一个Actvity完全被遮住时,它将进入Stopped状态。
  • Stopped 状态:当Activity是不可见的时,Activity处于Stopped状态。Activity将继续保留在内存中保持当前的所有状态和成员信息,假设系统别的地方需要内存的话,这时它是被回收对象的主要候选。当Activity处于Stopped状态时,一定要保存当前数据和当前的UI状态,否则一旦Activity退出或关闭时,当前的数据和UI状态就丢失了。
  • Inactive状态:Activity被杀掉以后或者被启动以前,处于Inactive状态。这时Activity已被移除从Activity堆栈中,需要重新启动才可以显示和使用。
         状态过渡具有不确定性并且由OPhone Runtime完全管理。OPhone Runtime将首先杀掉处于Stopped状态的Activity,在极端情况下,也会杀掉那些处于Paused状态的Activity。 
为确保无缝的用户体验,这些状态之间的过渡对用户来说应该做到透明的。不管Activity处于那种状态,最重要的是保留好UI状态和用户数据,一旦Actvity被激活,用户都能看到他想要的东西。
如何监测Actvity的状态变化

         为了确保Activity能够及时的响应状态的变化,OPhone提供了一系列的事件处理程序来处理Activity的状态转移,参考下图和示例代码。

  • public  
    class   MyActivity   extends   Activity {   
  • // 在Activity生命周期开始时被调用 

    public 
    void  onCreate(Bundle icicle) {   
  • }  
    // onCreate完成后被调用,用来回复UI状态 
  • public 
    void  onRestoreInstanceState(Bundle savedInstanceState) {   
                }  
  •           //当activity从停止状态重新启动时调用 

              public 
    void  onRestart(){   
  •           }  
              //当activity对用户即将可见的时候调用。 
  •           public 
    void  onStart(){   
              }  
  • //当activity将要与用户交互时调用此方法,此时activity在activity栈的栈顶,用户输入已经         可以传递给它 

    public 
    void  onResume(){   
  • }  
    // Activity即将移出栈顶保留UI状态时调用此方法 
  • public 
    void  onSaveInstanceState(Bundle savedInstanceState) {   
    }  
  • // 当系统要启动一个其他的activity时调用(其他的activity显示之前),这个方法被用来提交那些持久数据的改变、停止动画、和其他占用 CPU资源的东西。由于下一个activity在这个方法返回之前不会resumed,所以实现这个方法时代码执行要尽可能快。 

    public 
    void  onPause(){   
  • }  
    //当另外一个activity恢复并遮盖住此activity,导致其对用户不再可见时调用。一个新activity启动、其它activity被切换至前景、当前activity被销毁时都会发生这种场景。 
  • public 
    void  onStop(){   
    }  
  • //在activity被销毁前所调用的最后一个方法,当进程终止时会出现这种情况 

    public 
    void  onDestroy(){   
  • }  

Java代码
  • public class MyActivity extends Activity {   

  • // 在Activity生命周期开始时被调用   

  • public void onCreate(Bundle icicle) {   
  • }   

  • // onCreate完成后被调用,用来回复UI状态   

  • public void onRestoreInstanceState(Bundle savedInstanceState) {   
  •             }   
  •           //当activity从停止状态重新启动时调用   
  •           public void onRestart(){   
  •           }   
  •           //当activity对用户即将可见的时候调用。   
  •           public void onStart(){   
  •           }   

  • //当activity将要与用户交互时调用此方法,此时activity在activity栈的栈顶,用户输入已经         可以传递给它   

  • public void onResume(){   
  • }   

  • // Activity即将移出栈顶保留UI状态时调用此方法   

  • public void onSaveInstanceState(Bundle savedInstanceState) {   
  • }   

  • //当系统要启动一个其他的activity时调用(其他的activity显示之前),这个方法被用来提交那些持久数据的改变、停止动画、和其他占用 CPU资源的东西。由于下一个activity在这个方法返回之前不会resumed,所以实现这个方法时代码执行要尽可能快。   

  • public void onPause(){   
  • }   

  • //当另外一个activity恢复并遮盖住此activity,导致其对用户不再可见时调用。一个新activity启动、其它activity被切换至前景、当前activity被销毁时都会发生这种场景。   

  • public void onStop(){   
  • }   

  • //在activity被销毁前所调用的最后一个方法,当进程终止时会出现这种情况   

  • public void onDestroy(){   
  • }  

public class MyActivity extends Activity {// 在Activity生命周期开始时被调用public void onCreate(Bundle icicle) {}// onCreate完成后被调用,用来回复UI状态public void onRestoreInstanceState(Bundle savedInstanceState) {            }          //当activity从停止状态重新启动时调用          public void onRestart(){          }          //当activity对用户即将可见的时候调用。          public void onStart(){          }//当activity将要与用户交互时调用此方法,此时activity在activity栈的栈顶,用户输入已经         可以传递给它public void onResume(){}// Activity即将移出栈顶保留UI状态时调用此方法public void onSaveInstanceState(Bundle savedInstanceState) {}//当系统要启动一个其他的activity时调用(其他的activity显示之前),这个方法被用来提交那些持久数据的改变、停止动画、和其他占用 CPU资源的东西。由于下一个activity在这个方法返回之前不会resumed,所以实现这个方法时代码执行要尽可能快。public void onPause(){}//当另外一个activity恢复并遮盖住此activity,导致其对用户不再可见时调用。一个新activity启动、其它activity被切换至前景、当前activity被销毁时都会发生这种场景。public void onStop(){}//在activity被销毁前所调用的最后一个方法,当进程终止时会出现这种情况public void onDestroy(){}        在一个Activity的完整的生命周期里,既创造和销毁之间,它会经过一个或多个不同状态之间的转移包括从可见的到不可见,从Active到Inactive。每一次状态的转移都将触发以上这些事件。
Activity完整的生命周期
完整的Activity生命周期之间从调用的OnCreate开始,到调用onDestroy结束。有可能在某些情况下,一个Activity被终止时并不调用onDestroy方法。      
         使用OnCreate方法来初始化你的Activity:初始化的用户界面,分配引用类变量,绑定数据控件,并创建服务和线程。在OnCreate方法传递的对象Bundle包含最后一次调用onSaveInstanceState保存的UI状态。你可以使用这个Bundle恢复用户界面到以前的状态,无论是在OnCreate方法或通过覆盖onRestoreInstanceStateMethod方法。
         覆盖onDestroy方法来清理OnCreate中创建的任何资源,并确保所有外部连接被关闭,例如网络或数据库的联系。
         为了避免创造短期对象和增加垃圾收集的时间,以致对用户体验产生直接影响。如果你的Activity需要创建一些对象的话,最好在onCreate方法中创建,因为它仅调用一次在一个Actvity的完整生命周期中。
Activity可见的生命周期

         一个Activity可见的生命周期始于OnStart调用,结束于OnStop调用。在这两个方法中间,你的Actvity将会对用户是可见的,尽管它可能没有焦点,也可能部分被遮挡着。在一个Activity完整的生命周期中可能会经过几个Activity可见的生命周期,因为你的Activity可能会经常在前台和后台之间切换。在极端情况下,OPhone Runtime将杀掉一个Activity即使它在可见状态并且并不调用onStop方法。
        OnStop方法用于暂停或停止动画,线程,定时器,服务或其他专门用于更新用户界面程序。当用户界面是再次可见时,使用OnStart(或onRestart)方法来恢复或重新启动这些程序,。
        onRestart方法优先于onStart被调用当一个Activity被重现可见时,使用它你可以实现一些Activity重新可见时的特殊的处理。
        OnStart / OnStop方法也被用来注册和注销专门用于更新用户界面Intent接收者。
Activity活跃的生命周期
一个Activity活跃的生命周期始于OnResume调用,结束于OnPause调用。一个活跃的Actvity总是在前台并且接收用户输入事件。当一个新的Actvity启动,或该设备进入休眠状态,或失去焦点,Activity活跃的生命周期就结束了。尽量在onPause和onResume方法中执行较量轻的代码以确保您的应用程序能够快速响应Acitvity在前台和后台之间切换。

         在调用onPause之前,onSaveInstanceState会被调用。这个方法提供了一个机会保存当前的UI状态到Bundle当中。 Bundle信息将会被传递到OnCreate和onRestoreInstanceState方法。使用onSaveInstanceState保存 UI状态(如检查按钮状态,用户焦点,未提交用户输入)能够确保目前相同的用户界面当Activity下次被激活时。在Activity活跃生命周期中,你可以安全地认为onSaveInstanceState和onPause将被调到即使当前进程将终止。
Activity生命周期示例

  • 父Activity启动子Activity,子Actvity退出,父Activity调用顺序如下
onCreate()
onStart()
onResume()
onFreeze()
onPause()
onStop()
onRestart()
onStart(),onResume() …

  • 用户点击Home,Actvity调用顺序如下
onCreate()
onStart()
onResume()
onFreeze()
onPause()
onStop() -- Maybe
onDestroy() – Maybe

  • 调用finish(), Activity调用顺序如下
onCreate()
onStart()
onResume()
onPause()
onStop() 
onDestroy()

  • 在Activity上显示dialog, Activity调用顺序如下
onCreate()
onStart()
onResume()

  • 在父Activity上显示透明的或非全屏的activity,Activity调用顺序如下
onCreate()
onStart()
onResume()
onFreeze()
onPause()

  • 设备进入睡眠状态,Activity调用顺序如下
onCreate()
onStart()
onResume()
onFreeze()
onPause()

异常情况生命周期
情况1.资源相关的系统配置发生改变

资源相关的系统配置发生改变,举个例子。当前Activity处于竖屏状态的时候突然转成横屏,系统配置发生了改变,Activity就会销毁并且重建,其onPause, onStop, onDestory均会被调用。因为实在异常情况下终止的,所以系统会调用onSaveInstanceState来保存当前Activity状态。这个方法是在onStop之前,与onPause没有固定的时序关系。当Activity重建的时候系统会把onSaveInstanceState所保存的Bundle作为对象传递给onRestoreInstanceState和onCreate方法。

情况2:资源内存不足导致低优先级Activity被杀死

Activity优先级
前台Activity——正在和用户交互的Activity,优先级最高
可见但非前台Activity——Activity中弹出的对话框导致Activity可见但无法交互
后台Activity——已经被暂停的Activity,优先级最低
系统内存不足是,会按照以上顺序杀死Activity,并通过onSaveInstanceState和onRestoreInstanceState这两个方法来存储和恢复数据。

Activity的启动模式

四种启动模式分别是standard(标准模式)、singleTop(栈顶复用模式)、singleTask(栈内复用模式)、singleInstance(单实例模式 - 加强的singleTask模式)

standard
  • 系统默认启动模式
  • 不论存在与否,都会重新创建一个新的实例
  • 多实例实现,谁启动了这个Activity、那么这个Activity就运行在启动它那个Activity所在栈
singleTop
  • 判断需要启动的Activity是否为任务栈栈顶 ,如果是,则不会重新创建,如果不是,则会重新创建
  • 不重新创建时候,该Activity的 onNewIntent(Intent intent) 方法会被回调,通过该方法的参数,可以取出当前请求的信息;
  • 系统可能会杀死该Activity,杀死之后,启动情况与第一次启动相同,所以有必要在onCreate与onNewIntent方法中调用同一个处理数据的方法

    运用场景:常运用于通知栏弹出Notification,点击Notification跳转到指定的Activity,设置singleTop模式

singleTask
  • 判断Activity所需任务栈内是否已经存在,如果存在,就把该Activity切换到栈顶(会导致在它之上的都会出栈)
  • 如果所需任务栈都不存在,就会先创建任务栈再创建该Activity
  • 可以理解为 顶置Activity+singleTop 的模式

    运用场景:可用来退出整个应用。主界面activity设为singleTas模式,要退出应用时转到主activity,从而将主activity之上的activity都清除,然后重写主activity的onNewIntent()方法,在里面加上finish(),即可退出所有activity。这种模式还适用于做浏览器、微博之类的应用

singleInstance
  • 拥有singleTask的所有特性之外,此模式Activity只能单独地位于一个新的任务栈中
  • 也就是,Activity启动之后,就会独自在一个新的任务栈中,下次肯定不会重新创建该Activity,除非被系统杀死

    运用场景:这种模式常运用于需要与程序分离的界面,如在SetupWizard(安装向导)中调用紧急呼叫就是适用这种模式

Intent和Intent-filter的匹配规则

1、Intent和Intent Filter的介绍

Intent是抽象的数据结构,包含了一系列描述某个操作的数据,使得程序在运行时可以在程序中不同组件间通信或启动不同的应用程序。
可以通过startActivity(Intent)启动一个Activity,sendBroadcast(Intent))发送广播发送给感兴趣的BroadcastReceiver组件,startService(android.content.Intent))启动service,bindService()绑定服务。
Intent Filter顾名思义就是Intent的过滤器,组件通过定义Intent Filter可以决定哪些隐式Intent可以和该组件进行通讯
Intent分为隐式(implicit)Intent和显式(explicit)Intent。
显式Intent通常用于在程序内部组件间通信,已经明确的定义目标组件的信息,所以不需要系统决策哪个目标组件处理Intent,如下

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

其中CRListDemo和GoogleMapDemo都是用户自定义的组件,
隐式Intent不指明目标组件的class,只定义希望的Action及Data等相关信息,由系统决定使用哪个目标组件。如发送短信

2.Intent和Intent-filter的匹配规则

Android系统通过对Intent和目标组件在AndroidManifest文件中定义的(也可以在程序中定义Intent Filter)进行匹配决定和哪个目标组件通讯。如果某组件未定义则只能通过显式的Intent进行通信。Intent的三个属性用于对目标组件选取的决策,分别是Action、Data(Uri和Data Type)、Category。匹配规则如下

  • action匹配规则:要求intent中的action 存在 且 必须和过滤规则中的其中一个相同 区分大小写;
  • category匹配规则:系统会默认加上一个android.intent.category.DEAFAULT,所以intent中可以不存在category,但如果存在就必须匹配其中一个;
  • data匹配规则:data由两部分组成,mimeType和URI,要求和action相似。如果没有指定URI,URI但默认值为content和file(schema)
3.利用Intent调用其他常见程序

a. 发送短信

Uri uri = Uri.parse("smsto:15800000000");
Intent i=newIntent(Intent.ACTION_SENDTO, uri);
i.putExtra("sms_body", "The SMS text");
startActivity(i);

b. 打电话

Uri dial = Uri.parse("tel:15800000000");
Intent i=newIntent(Intent.ACTION_DIAL, dial);
startActivity(i);

c. 发送邮件

Uri email = Uri.parse("mailto:abc@126.com;def@126.com");
Intent i=newIntent(Intent.ACTION_SENDTO, email);
startActivity(i);

d. 拍照

Intent i =newIntent(MediaStore.ACTION_IMAGE_CAPTURE);
String folderPath= Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "AndroidDemo" +File.separator;
String filePath= folderPath + System.currentTimeMillis() + ".jpg";newFile(folderPath).mkdirs();
File camerFile=newFile(filePath);
i.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(camerFile));
startActivityForResult(i,1);

e. 浏览网页

Uri web = Uri.parse("http://www.google.com");
Intent i=newIntent(Intent.ACTION_VIEW, web);
startActivity(i);

f. 查看联系人

Intent i =newIntent();
i.setAction(Intent.ACTION_GET_CONTENT);
i.setType("vnd.android.cursor.item/phone");
startActivityForResult(i,1);

Activity过渡动画的五种实现

1.使用overridePendingTransition方法实现Activity跳转动画

overridePendingTransition方法是Activity中提供的Activity跳转动画方法,通过该方法可以实现Activity跳转时的动画效果,简单例子如下:

Intent intent =newIntent(MainActivity.this, SecondActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.slide_in_left, R.anim.slide_in_left);

注意:overridePendingTransition在startActivity或者是finish方法立刻执行才有效

2、使用style的方式定义Activity的切换动画

(1)定义Application的style

<!-- 系统Application定义 -->
<application 
Android:allowBackup="true"
Android:icon="@mipmap/ic_launcher" 
Android:label="@string/app_name" 
Android:supportsRtl="true" 
Android:theme="@style/AppTheme">

(2)定义具体的AppTheme样式
其中这里的windowAnimationStyle就是我们定义Activity切换动画的style。而@anim/slide_in_top就是我们定义的动画文件,也就是说通过为Appliation设置style,然后为windowAnimationStyle设置动画文件就可以全局的为Activity的跳转配置动画效果。

<!-- Base application theme. --> 
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
 <!-- Customize your theme here. --> 
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> 
<item name="colorAccent">@color/colorAccent</item>
<item name="Android:windowAnimationStyle">@style/activityAnim</item>
</style><!-- 使用style方式定义activity切换动画 -->
 <style name="activityAnim">
 <item name="Android:activityOpenEnterAnimation">@anim/slide_in_top</item>
 <item name="Android:activityOpenExitAnimation">@anim/slide_in_top</item> </style>

而在windowAnimationStyle中存在四种动画:

  • activityOpenEnterAnimation
    用于设置打开新的Activity并进入新的Activity展示的动画
  • activityOpenExitAnimation
    用于设置打开新的Activity并销毁之前的Activity展示的动画
  • activityCloseEnterAnimation
    用于设置关闭当前Activity进入上一个Activity展示的动画
  • activityCloseExitAnimation
    用于设置关闭当前Activity时展示的动画
3.使用ActivityOptions切换动画实现Activity跳转动画

通过overridePendingTransition方法基本上可以满足我们日常中对Activity跳转动画的需求了,但MD风格出来之后,overridePendingTransition这种老旧、生硬的方式怎么能适合我们的MD风格的App呢?google在新的sdk中给我们提供了另外一种Activity的过度动画——ActivityOptions。并且提供了兼容包——ActivityOptionsCompat。ActivityOptionsCompat是一个静态类,提供了相应的Activity跳转动画效果,通过其可以实现不少炫酷的动画效果。
(1)在跳转的Activity中设置contentFeature

@Override protected void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 
// 设置contentFeature,可使用切换动画 
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); 
Transition explode = TransitionInflater.from(this).inflateTransition(Android.R.transition.explode);
getWindow().setEnterTransition(explode); 
setContentView(R.layout.activity_three); }

(2)在startActivity执行跳转逻辑的时候调用startActivity的重写方法,执行ActivityOptions.makeSceneTransitionAnimation方法

/** * 点击按钮,实现Activity的跳转操作 * 通过Android5.0及以上代码的方式实现activity的跳转动画 */
button3.setOnClickListener(new View.OnClickListener() { 
@Override public void onClick(View v) { 
Intent intent = new Intent(MainActivity.this, ThreeActivity.class); 
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this).toBundle()); } });

activity跳转动画效果
(四)使用ActivityOptions之后内置的动画效果通过style的方式

这种方式其实就是通过style的方式展示和使用ActivityOptions过度动画,下面是实现通过定义style方式定义过度动画的步骤:
(1)编写过度动画文件

<explode xmlns:Android="http://schemas.Android.com/apk/res/Android" 
Android:duration="300" />

首先我们需要在Application项目res目录下新建一个transition目录,然后创建资源文件,然后使用这些系统自带的过渡动画效果,这里设置了过度时长为300ms。
(2)定义style文件

<!-- Base application theme. --> 
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> 
<!-- Customize your theme here. --> 
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> 
<item name="colorAccent">@color/colorAccent</item>
<item name="Android:windowEnterTransition">@transition/activity_explode</item>
<item name="Android:windowExitTransition">@transition/activity_explode</item> 
</style>

在Application的style文件中添加:

<item name="Android:windowEnterTransition">@transition/activity_explode</item>
<item name="Android:windowExitTransition">@transition/activity_explode</item>

并指定过渡动画效果为我们刚刚定义的过渡动画文件。
(3)执行跳转逻辑
点击按钮,实现Activity的跳转操作 * 通过Android5.0及以上style的方式实现activity的跳转动画

button4.setOnClickListener(new View.OnClickListener() 
{ @Override public void onClick(View v) {
 /** * 调用ActivityOptions.makeSceneTransitionAnimation实现过度动画 */ 
Intent intent = new Intent(MainActivity.this, FourActivity.class); 
startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(MainActivity.this).toBundle()); 
}
 });

这样执行之后也可以展示出Activity跳转过度动画了,其和通过代码方式实现的效果是类似的,而且这种动画效果是全局的。

(五)使用ActivityOptions动画共享组件的方式实现跳转Activity动画

这里的共享组件动画效果是指将前面一个Activity的某个子View与后面一个Activity的某个子View之间有过渡效果,即在这种过度效果下实现Activity的跳转操作。那么如何实现两个组件View之间实现过渡效果呢?
(1)定义共享组件
在Activity a中的button按钮点击transitionName属性:

<Button Android:id="@+id/button5" 
Android:layout_width="match_parent" 
Android:layout_height="wrap_content" 
Android:layout_below="@+id/button4" 
Android:layout_marginTop="10dp" 
Android:layout_marginRight="10dp" 
Android:layout_marginLeft="10dp" Android:text="组件过度动画" 
Android:background="@color/colorPrimary" 
Android:transitionName="shareNames" />

在Activity b的布局文件中为组件定义transitionName属性,这样这两个组件相当于有了过度对应关系,这里需要注意的是这两个组件的transitionName属性的值必须是相同的。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
xmlns:Android="http://schemas.Android.com/apk/res/Android" 
Android:id="@+id/activity_second" 
Android:layout_width="match_parent" 
Android:layout_height="match_parent" 
Android:gravity="center_horizontal" Android:orientation="vertical" 
Android:transitionName="shareNames" > <TextView 
Android:layout_width="match_parent" 
Android:layout_height="match_parent" 
Android:background="@color/colorAccent" 
Android:layout_marginTop="10dp" 
Android:layout_marginBottom="10dp" />
</LinearLayout>

(2)调用startActivity执行跳转动画
点击按钮,实现Activity的跳转操作 * 通过Android5.0及以上共享组件的方式实现activity的跳转动画

 button5.setOnClickListener(new View.OnClickListener() { 
@Override public void onClick(View v) { 
Intent intent = new Intent(MainActivity.this, FiveActivity.class); 
startActivity(intent, 
ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, button5, "shareNames").toBundle()); } 
});

需要说明的是这里调用的ActivityOptions.makeSceneTransitionAnimation方法,传递了三个参数,其中第一个参数为context对象,第二个参数为启动Activity的共享组件,第三个参数为启动Activity的共享组件transitionName属性值。
这样经过调用之后我们就实现了从Activity a跳转到Activity b的时候a中的组件到b中组件的过度效果。


这里写图片描述

过渡动画总结

  • overridePendingTransition方法从Android2.0开始,基本上能够覆盖我们activity跳转动画的需求;
  • ActivityOptions API是在Android5.0开始的,可以实现一些炫酷的动画效果,更加符合MD风格;ActivityOptions还可以实现两个Activity组件之间的过度动画;
    过渡动画可参考github项目地址:实现activity跳转动画的五种方式

如果想在Activity中得到新打开Activity 关闭后返回的数据,需要使用系统提供的startActivityForResult(Intent intent, int requestCode)方法打开新的Activity,新的Activity 关闭后会向前面的Activity传回数据,为了得到传回的数据,必须在前面的Activity中重写onActivityResult(int requestCode, int resultCode, Intent data)方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public  class  MainActivity  extends  Activity {
 
     private  final  static  String TAG= "MainActivity" ;
 
     @Override
 
     public  void  onCreate(Bundle savedInstanceState) {
 
         super .onCreate(savedInstanceState);
 
         setContentView(R.layout.main);
 
        
 
         Button btnOpen=(Button) this .findViewById(R.id.btnOpen);
 
         btnOpen.setOnClickListener( new  View.OnClickListener(){
 
             public  void  onClick(View v) {
 
                 //得到新打开Activity关闭后返回的数据
 
                 //第二个参数为请求码,可以根据业务需求自己编号
 
                 startActivityForResult( new  Intent(MainActivity. this , OtherActivity. class ),  1 );
 
             }
 
         });
 
     }
 
     /**
 
      * 为了得到传回的数据,必须在前面的Activity中(指MainActivity类)重写onActivityResult方法
 
      *
 
      * requestCode 请求码,即调用startActivityForResult()传递过去的值
 
      * resultCode 结果码,结果码用于标识返回数据来自哪个新Activity
 
      */
 
     @Override
 
     protected  void  onActivityResult( int  requestCode,  int  resultCode, Intent data) {
 
         String result = data.getExtras().getString( "result" ); //得到新Activity 关闭后返回的数据
 
         Log.i(TAG, result);
 
     }
 
}

  

当新Activity关闭后,新Activity返回的数据通过Intent进行传递,android平台会调用前面Activity 的onActivityResult()方法,把存放了返回数据的Intent作为第三个输入参数传入,在onActivityResult()方法中使用第三个输入参数可以取出新Activity返回的数据。

使用startActivityForResult(Intent intent, int requestCode)方法打开新的Activity,新Activity关闭前需要向前面的Activity返回数据需要使用系统提供的setResult(int resultCode, Intent data)方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public  class  OtherActivity  extends  Activity {
 
  
 
     @Override
 
     protected  void  onCreate(Bundle savedInstanceState) {
 
         super .onCreate(savedInstanceState);
 
         setContentView(R.layout.other);
 
  
 
         Button btnClose=(Button)findViewById(R.id.btnClose);
 
         btnClose.setOnClickListener( new  View.OnClickListener(){
 
             public  void  onClick(View v) {
 
                 //数据是使用Intent返回
 
                 Intent intent =  new  Intent();
 
                 //把返回数据存入Intent
 
                 intent.putExtra( "result" "My name is linjiqin" );
 
                 //设置返回数据
 
                 OtherActivity. this .setResult(RESULT_OK, intent);
 
                 //关闭Activity
 
                 OtherActivity. this .finish();
 
             }
 
         });
 
        
 
     }
 
  
 
}
 
setResult()方法的第一个参数值可以根据业务需要自己定义,上面代码中使用到的RESULT_OK是系统Activity类定义的一个常量,值为- 1 ,代码片断如下:
 
public  class  android.app.Activity  extends  ......{
 
   public  static  final  int  RESULT_CANCELED =  0 ;
 
   public  static  final  int  RESULT_OK = - 1 ;
 
   public  static  final  int  RESULT_FIRST_USER =  1 ;
 
}

 

说明:当点击“打开新的Activity”按钮,会跳转到“我是新打开的Activity”页面;

        当点击“关闭”按钮,关闭当前页面,同时跳转到“我是旧的Activity”页面,且会传递result参数给前一个Activity              

使用startActivityForResult(Intent intent, int requestCode)方法打开新的Activity,我们需要为startActivityForResult()方法传入一个请求码(第二个参数)。请求码的值是根据业务需要由自已设定,用于标识请求来源。例如:一个Activity有两个按钮,点击这两个按钮都会打开同一个Activity,不管是那个按钮打开新Activity,当这个新Activity关闭后,系统都会调用前面Activity的onActivityResult(int requestCode, int resultCode, Intent data)方法。在onActivityResult()方法如果需要知道新Activity是由那个按钮打开的,并且要做出相应的业务处理,这时可以这样做:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Override   public  void  onCreate(Bundle savedInstanceState) {
 
         ....
 
         button1.setOnClickListener( new  View.OnClickListener(){
 
             public  void  onClick(View v) {
 
                 startActivityForResult ( new  Intent(MainActivity. this , NewActivity. class ),  1 );
 
            }
 
         });
 
         button2.setOnClickListener( new  View.OnClickListener(){
 
             public  void  onClick(View v) {
 
                  startActivityForResult ( new  Intent(MainActivity. this , NewActivity. class ),  2 );
 
             }
 
         });
 
                           
 
        @Override  protected  void  onActivityResult( int  requestCode,  int  resultCode, Intent data) {
 
                switch (requestCode){
 
                    case  1 :
 
                    //来自按钮1的请求,作相应业务处理
 
                    case  2 :
 
                    //来自按钮2的请求,作相应业务处理
 
                 }
 
           }
 
}

  

在一个Activity中,可能会使用startActivityForResult()方法打开多个不同的Activity处理不同的业务,当这些新Activity关闭后,系统都会调用前面Activity的onActivityResult(int requestCode, int resultCode, Intent data)方法。为了知道返回的数据来自于哪个新Activity,在onActivityResult()方法中可以这样做(ResultActivity和NewActivity为要打开的新Activity):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public  class  ResultActivity  extends  Activity {
 
        .....
 
        ResultActivity. this .setResult( 1 , intent);
 
        ResultActivity. this .finish();
 
}
 
public  class  NewActivity  extends  Activity {
 
        ......
 
         NewActivity. this .setResult( 2 , intent);
 
         NewActivity. this .finish();
 
}
 
public  class  MainActivity  extends  Activity {  // 在该Activity会打开ResultActivity和NewActivity
 
        @Override  protected  void  onActivityResult( int  requestCode,  int  resultCode, Intent data) {
 
                switch (resultCode){
 
                    case  1 :
 
                    // ResultActivity的返回数据
 
                    case  2 :
 
                     // NewActivity的返回数据
 
                 }
 
           }
 
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值