代码钢琴家 阅读(83) 评论(0)

Activity是什么?

Activity是一个可与用户交互并呈现组件的视图。通俗说就是运行的程序当前的这个显示界面。

如果你还不明白,那么你写过HTML吗,它就好比一个网页。我们程序中的用户视图,都是Activity类的一个实例。

 

Activity Manager与Activity回退栈

一个Android应用程序,可以有一个或者更多的Activity。Android系统通过一个Activity Manager 来管理所有应用程序的Activity,确切说

Activity Manager维护着一个Activity栈,也就是以栈的形式来管理Activity。这是什么意思呢?

每当程序创建并显示一个新的Activity,这个Activity就会被压到Activity栈的栈顶,原先的Activity就看不见了。置于栈顶的下一层。

从用户的角度看就是老Activity消失,这个Activity视图来到前台,显示。

当用户点击手机上的Back键,当前Activity就会被销毁,这个Activity从栈中弹出,下一层的Activity就会重新回到前台,显示。

这个机制就好比浏览器的回退按钮的作用:点击回退按钮,显示上一个浏览过的网页。

下面是一个抽象出来的Activity栈的模型图,并不是在向你解释手机液晶屏的制作工艺~

                              

 

Activity的生命周期及常用方法

 

官方那张图已经被传烂了。为了不占用篇幅,让你看起来很累,我把它放到文章的最后。

在一个应用程序运行期间,它包含的Activity有多种生命周期状态。作为开发者,你不必像C++程序员管理他们的堆内存一样,手动管理
这些Activity,它是由Activity Manager来管理的。

但是你可以在Activity的状态发生变化时,得到通知。
比如,当Activity第一次创建时,onCreate()就会被调用,当Activity被销毁时,onDestroy()函数被调用。
Activity还有一些其他的onXXX()形式的函数。

作为开发者,我们的主要精力就是Override这些函数,以便在Activiy发生相应的生命周期变化时,通过这些函数去处理(回应)一些我们
关注的工作。

 

• onCreate(Bundle savedInstanceState)

    这个函数在Activity第一次创建时调用。你可以在这个函数中做一些 "一次性工作",比如用户界面视图的初始化: setContentView(R.layout.xx)
   
    再比如获取组件的引用 mXXX = (T)findViewById(R.id.xxx)
    以及为组件绑定监听器 mXXX.setOnClickListener(new OnClickListener(){......});

   savedInstanceState是一个Bundle对象,Bundle,顾名思义,代表数据的捆绑体(数据包)。它的作用后面讲。

 


•onStart()

       当Activity显示并可见时,调用。

•onResume()

       当Activity可以与用户交互(可被操作)时调用,在这个方法中播放动画或者music是个好主意

•onPause()

      当Activity将被调回后台时调用,一般是因为某个其他的Activity出现在,挡住这个Activity而发生。

      在这方法中,你最好保存你需要永久保存的数据,比如数据库数据,或者编辑的文本。

•onStop()

      这个Activity完全不可见了,被压到后台。"我暂时不需要这个界面接口了","哪你就待在后台吧"
      注意:如何内存使用紧缺,android会直接kill掉程序的进程,这个方法可能来不及被调用,

•onRestart()

     "刚刚被丢在后台的那个Activity,我现在又需要你了,你出来吧"

     户从后台重新调回后台的Activity,然后onRestart()就会调用,接下来就调用onStart()方法。

      onStop() .............onRestart()....................onStart() ......onResume()
      丢到后台        调到前台从新开始             可见              可交互


•onDestroy()

     当Activity被销毁前,调用。

     注意:如何内存使用紧缺,android会直接kill掉程序的进程,这个方法可能来不及被调用.



从上面的概述来看,onStop()和onDestroy()方法会不会得到调用都是不能百分百保障的,因为一旦一个Activity被置于后台,在内存紧缺的
情况下,为了给新的Activity创建提供内存,android会直接干掉后台Activity的进程。
那么,最安全的阵营就是onPause()方法了。这是你必须知道的。

 

 

 

下面我们通过代码证实一下Activity的生命周期的转换机制。

 

MainActivity.java

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity
{

    public static String final TAG = "MainActivity";
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        
        Log.v(TAG,"onCreate");
        
    }

    
    @Override
    protected void onStart()
    {
        super.onStart();
        Log.v(TAG,"onStart");
    }
    
    
    @Override
    protected void onResume()
    {
        // TODO Auto-generated method stub
        super.onResume();
        Log.v(TAG,"onResume");
    }
    
    
    @Override
    protected void onPause()
    {
        // TODO Auto-generated method stub
        super.onPause();
        Log.v(TAG,"onPause");
    }
    
    
    @Override
    protected void onRestart()
    {
        // TODO Auto-generated method stub
        super.onRestart();
        Log.v(TAG,"onRestart");
    }


   
    @Override
    protected void onStop()
    {
        // TODO Auto-generated method stub
        super.onStop();
        Log.v(TAG,"onStop");
    }

    @Override
    protected void onDestroy()
    {
        // TODO Auto-generated method stub
        super.onDestroy();
        Log.v(TAG,"onDestroy");
    }
    
    
    
    
}

 

 

 

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" 
        android:layout_gravity="center_horizontal"
        android:text="Activity life cycle test"
        />

</LinearLayout>

 

 

最后的一个屏幕旋转演示时卡住了,只能说Android虚拟机太吃内存了,我得加内存条了  - - 。大家可以自己测试。

依次如下操作,并在LogCat中查看程序打印的日志

启动应用程序    : onCreate   ... onStart    ... onResume   

在onResume状态下,点击回退按钮  

                            onPause ... onStop ... onDestroy


在onResume状态下,点击Home按钮

                            onPause ... onStop
                             调出后台任务,重新回到这个Activity :

                                                                      onRestart ... onStart ... onResume

在onResume状态下,旋转屏幕:

                             onPause ... onStop ... onDestroy ... onCreate ... onStart ... onResume

 

 

 

Activity的数据保存与恢复

 

下面通过一个例子来说名Activity数据的保存和恢复的作用和过程。

例子的思路是这样的:在竖屏状态下,输入70,将初始为0 的 mTotal累加到70,并通过Toast显示,然后,切换手机为

横屏,再输入30,将mTotal累加到100,再次显示。

java文件

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity
{
private int mTotal = 0;   //累计储存变量
    private EditText mInputText = null;
    private Button mSubmitButton = null;

    
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        
        mInputText = (EditText)findViewById(R.id.id_input_text);
        
        mSubmitButton = (Button)findViewById(R.id.id_submit_button);
        mSubmitButton.setOnClickListener(new View.OnClickListener()
        {
            
            @Override
            public void onClick(View v)
            {
                try{
                    int inputNum = Integer.parseInt(mInputText.getText().toString());
                    mTotal+=inputNum;
                    showToast(String.format("total=%d", mTotal));
                }catch(Exception e)
                {
                    showToast("请输入一个数!!!");
                }
                
                
            }
        });
        
        
    }

    
    private void showToast(String s)
    {
        Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
    }


    
}

布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    
    <EditText
        android:id="@+id/id_input_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="numberSigned"    
        android:background="#55667A"
        android:textColor="#000000"
        android:textSize="20sp"

        />
<!--android:inputType="numberSigned"   表示只允许输入带符号的数-->
<Button 
android:id="@+id/id_submit_button"
android:layout_width
="wrap_content"
android:layout_height
="wrap_content"
android:layout_gravity
="right"
android:text
="提交" />


</LinearLayout>

 

 

 

                              

 

 

 

结果出乎意外,Toast显示为30,并不是100,。那么先前输入的70跑哪里去呢?

通过前面对Activity生命周期的状态切换的例子发现:当手机旋转后,原来的Activity被销毁了,取而代之的是一个新建的Activity实例。

而total变量是Activity实例的一个字段,也就是说,total为70时的Activity随着Activity销毁而销毁了。新的Activity创建时,新的mTotal被初始化为0。

所以mTotal的值并没有随着屏幕旋转得到保留

 

 

怎么解决这个问题呢?下面再介绍2个Activity的onXXX()形式的函数。

 

•onSaveInstanceState(Bundle outState)

      SaveInstanceState,很明显,意为保存实例状态(数据),on,表明它是一个触发函数。

      它的参数是一个Bundle类对象,也就是数据包对象,我们可以 将需要保存的数据,put到Bundle对象outState中。从数据结构的角度上说,Bundle其实就是

      一个Map,有的也叫Dictionary,是通过key - value的形式存取数据的。

     同样,onSaveInstanceState方法也是自动被调用的,也就是说,你不用关心onSaveInstanceState何时被调用, 你只需关心,你需要保存哪些数据 。

     确切说,onSaveInstanceState是在onPause() 调用之后被调用。
     这也符合常理,当Activity有被销毁的趋势时,就开始保存需要保存的数据,为下次创建使用。

     况且onPause()是最安全的阵营。onStop()和onDestroy()方法能不能被调用是不能百分百保证的。    

     如果不复写这个方法,默认情况下,这个方法会保存所有的控件状态数据。

     请注意上面图片中,EditText输入框中的内容70,它并没有在屏幕旋转后清空,说明默认的onSaveInstanceState()方法起到作用了。

     不信你可以测试一下,复写onSaveInstanceState方法,注释掉super.onSaveInstanceState(outState),输入数字旋转屏幕,会发现数字消失了。

     那么,复写这个方法的意义在于,在MVC的Model层,保存你自定义的数据。这正符合我们的意图:保存mTotal


•onRestoreInstanceState(Bundle savedInstanceState)
      上面介绍了如何暂存数据,这个当然就是如何将数据再取出来啦!

     当一个Activity重新创建并初始化时,这个方法就会调用,在这里重新获取先前通过onSaveInstanceState(Bundle outState)方法保存的Activity销毁

     前保留的数据。
     默认情况下,这个方法会重新初始化用户控件的状态。
     onRestoreInstanceState()会在onResume()之前调用,因为我们需要在activity可以与用户交互前做数据的恢复工作,这同样也符合常理。

 

下面开始fix前面mTotal例子的BUG!

修改后的java文件

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity
{

    //为存储的数据对象创建一个KEY
    public static final String KEY_MTOTAL = "MainActivity.MTOTAL";  
    
    private int mTotal = 0;
    private EditText mInputText = null;
    private Button mSubmitButton = null;

    
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        
        mInputText = (EditText)findViewById(R.id.id_input_text);
        
        mSubmitButton = (Button)findViewById(R.id.id_submit_button);
        mSubmitButton.setOnClickListener(new View.OnClickListener()
        {
            
            @Override
            public void onClick(View v)
            {
                try{
                    int inputNum = Integer.parseInt(mInputText.getText().toString());
                    mTotal+=inputNum;
                    showToast(String.format("total=%d", mTotal));
                }catch(Exception e)
                {
                    showToast("请输入一个数!!!");
                }
                
            }
        });
        
        
    }

    @Override
    protected void onSaveInstanceState(Bundle outState)
    {
        
        super.onSaveInstanceState(outState);     //存储空间状态
        outState.putInt(KEY_MTOTAL, mTotal);    //存储自定义数据,key - value形式
        
    }
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState)
    {
        super.onRestoreInstanceState(savedInstanceState);  //恢复控件状态
        
        if(savedInstanceState!=null)
        {
            //通过KEY,提取mTotal的值,最后一个参数0表示如果提取失败,则让mTotal为0
            mTotal = savedInstanceState.getInt(KEY_MTOTAL , 0);  
                
        }
        
    }

    
    private void showToast(String s)
    {
        Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();
    }


    
}

 

 

 

                                      

 

Bundle形象点说就时一个暂存数据的"包裹",它为横屏Activity和竖屏Activity之间数据传递搭建了一个桥梁。

Bundle对象中有很多putXXX和getXXX形式的方法,用来存取数据,这些数据都是通过key - value的形式存取的,所以你需要为存取的数据定义一个

独一无二的KEY值(String类型)。同时你还需要注意的是,Bundle只能存取一些常用的内置类型数据,如果你要存储你自定义的类型,则需要让你的类

实现Parcelable或者Serializable接口,限于篇幅,这里不再扩展,以后我会写出来分享。

细心的同学会发现,onCreate方法的参数也是一个Bundle,你也可以在onCreate方法中来恢复数据。不过我还是建议大家让每个函数各尽其职。

 

 

Activity与进程的关系


进程 != 程序

实质上讲,每一个用户操作视图,就是一个Activity的实例,每一个Activity的实例都有自己的生命周期。

应用程序就是一个或多个Activity,再加上容纳这些Activity并为Activity提供服务的进程。


在android中,即便一个应用程序的进程被干掉了,它仍然可以是“活着的”,因为
进程并不是和Activity绑定的。进程就像公交车,而Activity就是乘客,乘客的一天也是有不同的状态的,天亮了,起床乘公交
上班,中午,乘公交回家吃饭,下午,乘公交回家。可以发现,同样的道理,乘客并不依赖某一辆公交,比如上班的时候坐的是25路,
回家也可以坐32路公交,只要这辆车能为乘客完成自己的状态转换就行。没有公交车,乘客永远不能完成自己的状态转换,
没有进程,Activity也不能完成自己生命周期的切换,这也是为什么onStop()和onDestroy()方法不能得到调用的原因。