Android架构设计实践之Activity

本节重点介绍一下Android架构设计实践开发中Activity相关内容:废话不多说,先来一张思维导图帮助大家系统的对Activity有一个大概的了解。


Android架构设置实践之Activity.png

Activity定义:

Activity是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。每个Activity都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。

Activity生命周期:

activity生命周期.png

如图所示,想要理解Activity的生命周期其实主要抓住三个关键循环,四种基本状态以及七个生命周期的方法即可。

Activity生命周期的三个关键循环:

1、 Activity完整的生命周期:
onCreate(Bundle)开始到onDestroy()结束。ActivityonCreate()设置所有的全局状态,在onDestory()释放所有的资源。例如:某个Activity有一个在后台运行的线程,用于从网络下载数据,则该Activity可以在onCreate()中创建线程,在onDestory()中停止线程。

2、 Activity可见的生命周期:
onStart()开始到onStop()结束。在这段时间,可以看到Activity在屏幕上,尽管有可能不在前台,不能和用户交互。在这两个接口之间,需要保持显示给用户的UI数据和资源等,例如:可以在onStart()中注册一个IntentReceiver来监听数据变化导致UI的变动,当不再需要显示的时候,可以在onStop()中注销它,onStart(),onStop()都可以被多次调用,因为Activity随时可以在可见和隐藏之间转换。

3、 Activity前台的生命周期:
onResume()开始到onPause()结束。在这段时间里,该Activity处于所有Activity的最前面,和用户进行交互。Activity可以经常性的在resumedpaused状态之间切换,例如:当设备准备休眠时,当一个Activity处理结果被分发时,当一个新的Intent被分发时。所有在这些接口方法中的代码应该属于非常轻量级的。

Activity四种基本状态:

1、 Active/Running(活动状态)
Activity运行在屏幕前台(处于当前任务活动栈的最上面),此时它获取了焦点能响应用户的操作,属于运行状态,同一个时刻只有一个Activity处于活动(Active)或运行(Running)状态。

2、 Paused(暂停状态)
Activity失去焦点但仍对用户可见(如在它之上有另一个透明的ActivityToastAlertDialog等弹出窗口时)它处于暂停状态。暂停的Activity仍然是存活状态(它保留着所有的状态和成员信息并保持和窗口管理器的连接),但是当系统内存极小时可以被系统杀掉。

3、 Stopped(停止状态)
完全被另一个Activity遮挡时处于停止状态,它仍然保留着所有的状态和成员信息。只是对用户不可见,当其他地方需要内存时它往往被系统杀掉。

4、 Dead(非活动状态)
Activity尚未被启动、已经被手动终止,或已经被系统回收时处于非活动状态,要手动终止Activity,可以在程序中调用finish()方法。如果是(按根据内存不足时的回收机制)被系统回收,可能是因为内存不足了,内存不足时,Dalvak虚拟机会根据其内存回收规则来回收内存:
(1). 先回收与其他ActivityService/Intent Receiver无关的进程(即优先回收独立的Activity)因此建议,我们的一些耗时后台操作,最好是放在Service中进行。
(2). 不可见(处于Stopped状态的Activity)。
(3). Service进程(除非真的没有内存可用时会被销毁)。
(4). 非活动的可见的(处于Paused状态的Activity)。
(5). 当前正在运行(处于Active/Running状态的Activity)。

Activity生命周期的七个方法:

i. protected void onCreate(Bundle savedInstanceState)一个Activity的实例被启动时调用的第一个方法。一般情况下,我们都覆盖该方法作为应用程序的一个入口点,在这里做一些初始化数据、设置用户界面等工作。大多数情况下,我们都要在这里从 xml 中加载设计好的用户界面。例如:

setContentView(R.layout.main);

当然,也可从savedInstanceState中读我们保存到存储设备中的数据,但是需要判断 savedInstanceState是否为 null,因为 Activity第一次启动时并没有数据被存贮在设备中:

if(savedInstanceState!=null){ 
savedInstanceState.get("Key"); 
}

ii. protected void onStart()该方法在onCreate()方法之后被调用,或者在 ActivityStop状态转换为 Active状态时被调用。

iii. protected void onResume()ActivityPause 状态转换到 Active状态时被调用。

iv. protected void onPause()ActivityActive 状态转换到 Pause状态时被调用。

v. protected void onStop()ActivityActive状态转换到 Stop 状态时被调用。一般我们在这里保存 Activity的状态信息。

vi. protected void onDestroy()Active 被结束时调用,它是被结束时调用的最后一个方法,在这里一般做些释放资源,清理内存等工作。
以上方法的调用时机如图所示:

Activity生命周期方法调用时机.png

Activity的启动模式:

Activity的启用模式也较为简单,它会在活动切换时用到。Activity的启动模式分为四种,standardsingleTopsingleTasksingleInstance模式。接下来将为大家详细的介绍一下这几种加载模式。
Activity的加载模式可以在配置文件AndroidManifest.xml中进行配置,配置项为android:launchMode具体如下图所示:

activity启动模式配置.png

1、 standard模式
Activity的栈中无论该活动有没有加入栈,活动就会被创建。测试方式是把MainActivitylaunchMode设置成standard, 在MainActivity中添加一个按钮,点击按钮使用Intent跳转到当前Activity,看onCreate方法中打印的Log。点击按钮的方法如下:

         Button launchModelButton = (Button) findViewById(R.id.launch_model_button);
         launchModelButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 Intent intent = new Intent(MainActivity.this, MainActivity.class);
                 startActivity(intent);
             }
         });

standard加载模式的栈如下所示,无论栈中有没有该对象的实例,都会被创建。

standard栈.png

2、 singleTop模式
只要被创建的活动不位于栈的顶部,该活动就会被创建入栈。如果将要被创建的活动位于栈的顶部,该活动的实例就不会被创建。测试方法,把上面的模式直接改成singleTop模式,MainActivty往自己身上跳转就不会从新创建一个新的实例,会重用之前在栈顶中的实例。如果是MainActivty跳转到SecondActivty, 然后SecondActivity再次跳转到MainActivty, 那么此时的MainActivity将会被创建,因为栈顶是SecondActivity。如下所示:

singleTop栈.png

3、 singleTask模式
单任务模式,这个也不难理解,如果从MainActivty跳转到SecondActivity, 如果再从SecondActivty跳转到MainActivity, 在单任务模式下MainActivity已经在栈中,就会把它之前的Activity出栈,使其处于在栈顶活跃的位置。原理如下图所示:

singleTask栈.png

4、 singleInstance模式
可以看成单例模式,这个比较特殊,被设置成 singleInstanceActivity将会放入另一个栈中,因为这样为了便于共用。上面3中模式位于同一个栈中。下方 ThirdActivity跳转到一个加载模式为 singleInstanceActivity中。

singleInstance栈.png

Activity的启动过程:

可以将Activity的启动过程简单总结为以下六个过程
1、使用代理模式启动到ActivityManagerService中执行。
2、创建ActivityRecordmHistory记录中。
3、通过socket通信到Zygote相关类创建process
4、通过ApplicaitonThreadActivityManagerService建立通信。
5、ActivityManagerService通知ActivityThread启动Activity的创建。
6、ActivityThread创建Activity加入到mActivities中并开始调度Activity执行。

Activity的通信方式:

1、 使用Intent 通信
Android 中,不同的 Activity 实例可能运行在一个进程中,也可能运行在不同的进程中。因此我们需要一种特别的机制帮助我们在 Activity 之间传递消息。Android中通过 Intent对象来表示一条消息,一个 Intent 对象不仅包含有这个消息的目的地,还可以包含消息的内容,这好比一封Email,其中不仅应该包含收件地址,还可以包含具体的内容。对于一个 Intent 对象,消息“目的地”是必须的,而内容则是可选项。
在上面的实例中通过 Activity. startActivity(intent)启动另外一个 Activity的时候,我们在 Intent类的构造器中指定了“收件人地址”。
如果我们想要给“收件人”Activity说点什么的话,那么可以通过下面这封“e-mail”来将我们消息传递出去:

Intent intent =new Intent(CurrentActivity.this,OtherActivity.class);
 // 创建一个带“收件人地址”的 email 
Bundle bundle =new Bundle();// 创建 email 内容
bundle.putBoolean("boolean_key", true);// 编写内容
bundle.putString("string_key", "string_value"); 
intent.putExtra("key", bundle);// 封装 email 
startActivity(intent);// 启动新的 Activity

那么“收件人”该如何收信呢?在 OtherActivity类的 onCreate()或者其它任何地方使用下面的代码就可以打开这封“e-mail”阅读其中的信息:

Intent intent =getIntent();// 收取 email 
Bundle bundle =intent.getBundleExtra("key");// 打开 email 
bundle.getBoolean("boolean_key");// 读取内容
bundle.getString("string_key");

上面我们通过bundle对象来传递信息,bundle维护了一个 HashMap<String, Object>对象,将我们的数据存贮在这个HashMap中来进行传递。但是像上面这样的代码稍显复杂,因为Intent 内部为我们准备好了一个 bundle,所以我们也可以使用这种更为简便的方法:

Intent intent =new Intent(EX06.this,OtherActivity.class); 
intent.putExtra("boolean_key", true); 
intent.putExtra("string_key", "string_value"); 
startActivity(intent);

接收:

Intent intent=getIntent(); 
intent.getBooleanExtra("boolean_key",false); 
intent.getStringExtra("string_key");

2、 使用 SharedPreferences
SharedPreferences 使用 xml格式为 Android 应用提供一种永久的数据存贮方式。对于一个Android应用,它存贮在文件系统的/data/ data/your_app_package_name/shared_prefs/目录下,可以被处在同一个应用中的所有 Activity 访问。Android提供了相关的 API来处理这些数据而不需要程序员直接操作这些文件或者考虑数据同步问题。

// 写入 SharedPreferences 
SharedPreferences preferences = getSharedPreferences("name", MODE_PRIVATE); 
Editor editor = preferences.edit(); 
editor.putBoolean("boolean_key", true); 
editor.putString("string_key", "string_value"); 
editor.commit(); 
        
// 读取 SharedPreferences 
SharedPreferences preferences = getSharedPreferences("name", MODE_PRIVATE); 
preferences.getBoolean("boolean_key", false); 
preferences.getString("string_key", "default_value");

3、 其它方式
Android 提供了包括 SharedPreferences 在内的很多种数据存贮方式,比如 SQLite,文件等,程序员可以通过这些 API 实现 Activity 之间的数据交换。如果必要,我们还可以使用IPC 方式。

Activity的Intent Filter:

Intent Filter描述了一个组件愿意接收什么样的 Intent 对象,Android将其抽象为 android.content.IntentFilter 类。在 AndroidAndroidManifest.xml配置文件中可以通过 <intent-filter >节点为一个 Activity 指定其Intent Filter,以便告诉系统该 Activity 可以响应什么类型的 Intent
当程序员使用 startActivity(intent) 来启动另外一个 Activity 时,如果直接指定intent 了对象的 Component属性,那么Activity Manager 将试图启动其 Component 属性指定的Activity。否则Android将通过 Intent的其它属性从安装在系统中的所有 Activity中查找与之最匹配的一个启动,如果没有找到合适的Activity,应用程序会得到一个系统抛出的异常。这个匹配的过程如下:

intentfilter匹配.jpg

Action匹配:

Action 是一个用户定义的字符串,用于描述一个 Android应用程序组件,一个 Intent Filter可以包含多个 Action。在 AndroidManifest.xmlActivity定义时可以在其 <intent-filter >节点指定一个 Action 列表用于标示 Activity所能接受的“动作”,例如:

<intent-filter > 
 <action android:name="android.intent.action.MAIN" /> 
 <action android:name="com.zy.myaction" /> 
……
 </intent-filter>

如果我们在启动一个 Activity时使用这样的 Intent对象:

Intent intent =new Intent(); 
intent.setAction("com.zy.myaction");

那么所有的Action 列表中包含了“com.zy.myaction”Activity 都将会匹配成功。
Android预定义了一系列的 Action 分别表示特定的系统动作。这些 Action通过常量的方式定义在 android.content. Intent中,以“ACTION_”开头。我们可以在Android提供的文档中找到它们的详细说明。

URI 数据匹配:

一个Intent 可以通过 URI 携带外部数据给目标组件。在<intent-filter > 节点中,通过<data/> 节点匹配外部数据。
mimeType 属性指定携带外部数据的数据类型,scheme 指定协议,host、port、path 指定数据的位置、端口、和路径。如下:

<data android:mimeType="mimeType" android:scheme="scheme"
android:host="host" android:port="port" android:path="path"/>

如果在Intent Filter 中指定了这些属性,那么只有所有的属性都匹配成功时URI 数据匹配才会成功。

Category 类别匹配:

<intent-filter >节点中可以为组件定义一个 Category 类别列表,当 Intent 中包含这个列表的所有项目时 Category 类别匹配才会成功。

其他:

Activity被回收了怎么办

当我们的临时数据还存放在活动中,可活动被回收了怎么办?这样的情况明显是常常会遇见的。查阅文档可以发现Activity提供了一个onSaveInstanceState()回调方法,这个方法可以保证活动在回收前一定可以调用。所以我们可以利用这个方法,在活动被销毁前,将一些我们期望保留的数据保存下来。

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    String tempData = "Something you want to save";
    outState.putString("data key", tempData);
}

可以看到onSaveInstanceState()携带了一个Bundle类型的参数,Bundle类提供了一系列的方法用于保存数据,每个保存方法需要传入两个参数:取出数据时用到的键值和数据对象。
然后取出的数据可以在onCreate中去恢复,因为它也有一个Bundle类型参数,这个参数在一般情况下都是 null,但是当活动被系统回收之前有通过 onSaveInstanceState()方法来保存数据的话,这个参数就会带有之前所保存的全部数据,我们只需对Bundle对象调用getString()方法并传入保存时写入的键值即可,当然我们在取出数据之前最好先判断一下Bundle对象是否为空。

Activity全屏设置

方式1:AndroidManifest.xml

<activity android:name="MainActivity"  android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />

方式2:代码实现

requestWindowFeature(Window.FEATURE_NO_TITLE);  // 隐藏标题栏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);  // 隐藏状态栏

注意:设置全屏的代码必须在setContentView(R.layout.main)之前,不然会报错。

Activity横竖屏设置

方式1:AndroidManifest.xml

<activity android:name="MainActivity"  android:screenOrientation="landscape" />  // 或者 “portrait”

方式2:代码实现

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

Activity获取横竖屏方向

int orientation = this.getResources().getConfiguration().orientation;
orientation 的常用取值可以为 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE(横屏) 或 ActivityInfo.SCREEN_ORIENTATION_PORTRAIT(竖屏)

Activity屏幕一直显示

1、AndroidManifest.xml添加权限

<uses-permission android:name="android.permission.WAKE_LOCK" />

2、代码实现

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容