Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Android四大组件--Activity详解(一)

$
0
0

Android四大组件中我相信Activity绝对是大家最熟悉的,但是虽然我们几乎是天天在用它,但真的有好好的了解过它么。如果你没有,那就可以看看这篇博客了,它或许没有什么高深的内容,但也是好好的对Activity整理了一番,相信还是能够让你又所收获的。

1、什么是Activity

第一点当然是讲我们一直在使用的Activity是什么东西。Activity本质上就是一个承载我们应用页面的框架,可以用于显示View。Activity是一个与用户交互的系统模块,几乎所有的Activity都是和用户进行交互的。

对于Activity是什么,我们就应该知道它在Android中处于一个怎样的位置,这里我们就要来聊聊MVC设计模式:

  • M(Model模型):Model是应用程序的主体部分,所有的业务逻辑都应该写在这里,如:对数据库的操作,对网络等的操作都放在该层(但不是说它们都放在同一个包中,可以分开放,但它们统称为Model层)。
  • V(View视图):是应用程序中负责生成用户界面的部分,也是在整个MVC架构中用户唯一可以看到的一层,接收用户输入,显示处理结果;在Android应用中一般采用XML文件里德界面的描述,使用的时候可以非常方便的引入,当然也可以使用JavaScript+Html等方式作为View。
  • C(Controller控制层)android的控制层的重任就要落在众多的activity的肩上了,所以在这里就要建议大家不要在activity中写太多的代码,尽量能过activity交割Model业务逻辑层处理。

在介绍过Android应用开发中的MVC架构后,我们就可以很明确的知道,在Android中Activity主要是用来做控制的,它可以选择要显示的View,也可以从View中获取数据然后把数据传给Model层进行处理,最后再来显示出处理结果。

2、Activity生命周期

我们在实际开发中少不了和Activity的生命周期打交道,我们的应用常要在onPause(),onResume(),onDestroy()这样的方法里暂停资源,开启资源和释放资源,所以我们了解生命周期是很有必要的。首先当然是上一张谷歌官方给我们提供的图啦:

相信大家对这个图应该不陌生,这些方法也都认得,所以我就只讲一下Activity各个方法的调用过程。

Activity有四种状态:

  • Running状态:一个新的Activity启动入栈后,它在屏幕最前端,处于栈的最顶端,此时它处于可见并可和用户交互的激活状态。
  • Paused状态:当Activity被另一个透明或者Dialog样式的Activity覆盖时的状态。此时它依然与窗口管理器保持连接,系统继续维护其内部状态,它仍然可见,但它已经失去了焦点,故不可与用户交互。
  • Stopped状态:当Activity不可见时,Activity处于Stopped状态。当Activity处于此状态时,一定要保存当前数据和当前的UI状态,否则一旦Activity退出或关闭时,当前的数据和UI状态就丢失了。
  • Killed状态:Activity被杀掉以后或者被启动以前,处于Killed状态。这是Activity已从Activity堆栈中移除,需要重新启动才可以显示和使用。

4种状态中,Running状态和Paused状态是可见的,Stopped状态和Killed状态时不可见的。

Running相当于Resumed,Killed相当于Destoryed。

首先打开我们的应用,就会调用onCreate()创建我们的视图,然后接着调用onStart()方法,onStart()是Activity界面被显示出来的时候执行的,用户可见,用户可以看到部分Activity但不能与它交互。

调用onResume()方法后用户就可以获得Activity的焦点,能够与Activity交互。用户看到的Activity多是处于Running阶段。

后面的几个方法是在有突发情况发生时调用的。onPause()(通常是当前的Acitivty被暂停了,比如被另一个透明或者Dialog样式的Activity覆盖了),之后dialog取消,Activity回到可交互状态,调用onResume()。
onStop()(也就是用户按下了home键,Activity变为后台后,当然在这个过程中也会调用onPause()),之后用户再切换回这个activity就会调用onRestart()而后调用onStart()。

大家要知道,我们的Activity在内存中是以栈的形式,如果内存不足,系统会自动把后台中优先级低的Kill掉,所以无论是Paused状态还是Stopped状态Activity都有可能被系统回收掉。

这是单个Activity的生命周期过程,相信对大家没什么难度,接下来是多个Activity交互时的生命周期,这个就要复杂的多了,我们用代码例子来演示:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "onCreate");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }

    public void click(View v) {
        Intent intent = new Intent(MainActivity.this, SecondActivity.class);
        startActivity(intent);
    }
}

在SecondActivity中与MainActivity一样只是在那几个方法中打印log而已,就不贴出来了。

我们从MainActivity跳转到SecondActivity中,生命周期的方法调用顺序如上图所示。经过上面的介绍我们知道onPause()是不让用户和Activity进行交互,onStop()则是Activity不可见,所以在SecondActivity获取焦点前用户看见的还是MainActivity,一旦获取焦点,则MainActivity不可见。

可以看到我按下了Back键,然后SecondActivity就调用了onPause()方法,因为MainActivity在后台中,所以调用onRestart()重新让它获得焦点。最后SecondActivity不可见接着被销毁。

从这些例子可以知道当要与其它Activity交互的时候当前Activity会先调用onPause(),虽然可见但不能与用户进行交互。

大家可能会对Activity的生命周期有这样几个疑问:

  • 为什么在打开新的Activity之前要先调用onPause()方法?
  • 为什么不在onPause()方法之后直接调用onStop()方法?

先调用onPause()当然是有必要的,比如我们正在当前Activity中看视频,如果这时候有电话打进来,但onPause()方法是在电话Activity进来后调用的话很有可能就会在打电话的同时还会听到视频的声音,这样的体验当然是不好的。所以谷歌在设计Activity生命周期的时候,每当有新的Activity进来或者要退出当前Activity的时候都会先调用onPause()方法,而我们就可以在onPause()方法里写我们暂停视频这样的逻辑。

之所以不接着就调用onStop()方法是谷歌出于安全的考虑,例如我们在启动这个SecondActivity的时候出现了Crush(闪退),那么如果先调用onStop(),那Activity就处于不可见的状态,而SecondActivity又已经Crush掉了,那用户看起来就很不对了,我们的程序会呈现黑屏的样子。所以谷歌让新的Activity成功启动了在让Activity隐藏到后台去。

3、保存数据

我们在切换横竖屏的时候,如果没有设置Activity不重启的话,我们的Activity是默认重启的,那么我们的数据就会流失了,所以我们要用方法将它保存下来才行,这里的知识我在我的博客Android横竖屏解析已经有过介绍,大家有兴趣的可以看看我的这篇博客。这篇博客提到了在onCreate()方法中使用savedInstanceState这个Bundle,但并没有介绍用法,我在这里提一下,只需要判断savedInstanceState不为空,其它都可以像博客里介绍的用onRestoreInstanceState()的方式去写。

4、Activity启动方式

一般来说,我们启动Activity有两种方式,一种是显式启动,通过Activity的名字调用startActivity()方法。另一种是匿名启动。

显式启动是用于启动我们自己创建的Activity,而匿名启动是启动其它的应用,例如我们想要用我们自己的应用启动微信,而我们是不可能知道微信的Activity的名字的,所以我们要通过Action匿名启动的方式。

首先我们来介绍一下显式启动的模式,在前面我讲Activity生命周期的时候我用到过只使用Intent的方式启动另一个Activity,这种方式就不提了,现在我换一种写法,使用ComponentName这个类。

public void click(View view) {
    Intent intent = new Intent();
    ComponentName component = new ComponentName(MainActivity.this, SecondActivity.class);
    intent.setComponent(component);
    startActivity(intent);
}

这样同样可以达到在MainActivity中启动SecondActivity的效果。

显示启动的用法比较简单,我们的重点还是在匿名启动上。例如要想在MainActivity中隐式的启动SecondActivity,我们需要在Manifest中为SecondActivity设置Intent-filter(意图过滤器)。

<activity android:name=".SecondActivity">
    <intent-filter>
          <action android:name="www.luhaotian.com"/>
          <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

action的名字因为是我们自己的Activity,所以可以任我们选择。

public void click(View v) {
    Intent intent = new Intent();
    intent.setAction("www.luhaotian.com");
    startActivity(intent);
}

隐式启动主要是用来启动不是我们本应用的Activity,我们这里其实还是启动我们本地的Activity,如果是要启动浏览器,或是系统相册这样的我们就要使用隐式的启动方式。

我们来介绍几个常见的Action:

Intent.ACTION_VIEW:当我们为Intent设置为这个Action,则所有Activity的action为ViewAction的都会响应。浏览器的Action也是这个,但如果我们只要启动浏览器应用,还需要为Intent加上URI对象的data。

Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri uri = Uri.parse("http://www.baidu.com");
intent.setData(uri);
startActivity(intent);

这样我们只要在响应的浏览器中选择一个打开就会跳转到百度页面了。

我们还可以使用Intent.ACTION_VIEW这个Action实现拨打电话的功能。

Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri uri = Uri.parse("tel:123456");
intent.setData(uri);
startActivity(intent);

Intent.ACTION_GET_CONTENT:我们可以用这个Action打开系统相册。不过同样有很多应用的action是这个,要想精确到相册,我们需要设置Intent的type。

Intent intent = new Intent();
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivity(intent);

Intent.ACTION_SEND:这个Action可以用来打开发送消息的Activity。

Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, "luhaotian");
startActivity(intent);

用上面的写法我们就可以看到在打开Activity的时候,输入框里就有我们传入的文字内容。

5、传递数据

我们在前面讲了横竖屏切换是通过onSaveInstanceState()方法保存Bundle的值,那两个Activity之间怎么进行数据交互呢。为了节省流量,我们都是将前一个页面已有的信息传送到另一个页面。

比如我们在CSDN上点开博客列表里的一篇博客,跳转到博客页面,而标题的文字内容是从前一个页面获取的。

我们在启动一个Activity的时候给它传入一个String类型的值和一个int类型的值:

public void click(View v) {
    Intent intent = new Intent(MainActivity.this, SecondActivity.class);
    intent.putExtra("name", "haotian");
    intent.putExtra("age", "20");
    startActivity(intent);
}
public class SecondActivity extends AppCompatActivity {

    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        tv = (TextView) findViewById(R.id.tv);
        Intent intent = getIntent();
        if (intent != null) {
            String name = intent.getStringExtra("name");
            int age = intent.getIntExtra("age", 0);
            tv.setText("name=" + name + "  age=" + age);
        }
    }
}

Intent的putExtra()方法和getExtra()方法就是给我们来传递信息的,Intent有个putExtras()方法可以通过传递Bundle对象获取多个类型的值。我们来看看:

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
Bundle bundle = new Bundle();
bundle.putString("name", "haotian");
bundle.putInt("age", 20);
intent.putExtras(bundle);
startActivity(intent);

SecondActivity的代码不用修改,这是同样的效果。

但是可以看到我们的这样的写法只是传递几个Java的基本类型,如果我们要传一个大一点的,比如实体类的对象,那我们应该怎么写,首先我们把这个实体类创建出来:

public class Person implements Parcelable {

    private String name;
    private int age;
    private String address;

    public Person(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    protected Person(Parcel in) {
        name = in.readString();
        age = in.readInt();
        address = in.readString();
    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(address);
    }

    @Override
    public String toString() {
        return "name=" + name + "  age=" + age + "  address=" + address;
    }
}

这里我使用Parcelable做序列化处理,如果不了解的可以看我的博客Android–AIDL基础介绍。当然也可以使用Serializable,这个就只要继承然后用putSerializable()和getSerializableExtra()方法获取信息。

Intent intent = new Intent(MainActivity.this, SecondActivity.class);
Bundle bundle = new Bundle();
Person person = new Person("haotian", 20, "白宫");
bundle.putParcelable("person", person);
intent.putExtras(bundle);
startActivity(intent);
public class SecondActivity extends AppCompatActivity {

    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        tv = (TextView) findViewById(R.id.tv);
        Intent intent = getIntent();
        if (intent != null) {
            Person person = intent.getParcelableExtra("person");
            tv.setText(person.toString());
        }
    }
}

我们通过Parcelable序列化也可以传递Bitmap对象:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
Bundle bundle = new Bundle();
bundle.putParcelable("bitmap", bitmap);

然后用同样的方法在另一个Activity把数据传入Bitmap中即可获得。

大家要注意的是Bundle虽然能为我们传递数据,但是它只能传较小的数据,如果传入的数据比较大,就会报Failed Binder Transaction这样一个错,Activity也是无法启动的。

结束语:本文仅用来学习记录,参考查阅。

作者:HardWorkingAnt 发表于2017/6/5 13:56:27 原文链接
阅读:164 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>