什么是Activity
官方定义:Activity是Android应用程序提供交互界面的一个重要界面,也是Android最重要的组件之一
自定义:Activity是业务类,是承载应用程序的界面以及业务行为的基础。
包括UI、Service……类似于我们的JavaBean
”脸“的表现都靠Activity了
什么是Intent
认为Intent就是在不同组件之间传递值而设计的一个数据结构
Intent属性:
extras——加入附加信息
category——IntentFilter
Action——Data,动作相关的值
ComponentName——Context
使用StarUML画UML图
Activity的启动过程
1、建立Activity类及定义属性和内部方法
2、注册Activity到Manifesst
3、在启动函数中(onCreate())实现业务
3.1 界面的定义layout
3.2 界面的绑定setContentView()
查看Activity源码
Activit.java中setContentView方法调用的是getWindow()的相关方法
getWindow返回的是Window类,Window类是一个抽象类,其调用的具体实现是PhoneWindow
查看PhoneWindow.java源码,可以看到setContentView的下一步实现
那么Activity与Window是什么关系呢
Activity构造的时候,会调用一个叫做attach的方法
attach最大的作用是通过PolicyManager.makeNewWindow(this)创建一个新的Window
并且把这个Window与Activity本身关联
PolicyManager最终调用的是Policy类
查看Policy.java的源码,它的makeNewWindow()方法返回一个新的PhoneWindow的对象
整体的关系如下图所示:
结论:
1、Activity构造的时候,调用了attach,绑定了一个window
2、Activity setContentView(),实际上是Window setContentView()
既然Activity实际上是Window来呈现的视图,那么setContentView也可以用Window来实现
this.getWindow().setContentView(R.layout.***);
还可以使用Inflate的方法进行设置
this.getWindow().setContentView(this.getLayoutInflater().inflate(R.layout.**));
Intent
跳转时,两种方式效果相同:
// 方法1
Intent intent = new Intent(A.this, B.class);
startActivity(intent);
// 方法2
Intent intent = new Intent();
intent.setClass(A.this, B.class);
startActivity(intent);
源码讲解startActivity()的机制
从Activity.java中可以看到startActivity方法
实际上startActivity()方法就是调用了startActivityForResult()方法,传了一个默认的-1作为requestCode
核心就是红框中的execStartActivity方法
具体的参数主要有:
this:上下文
mMainThread.getApplicationThread():主线程实例
mToken:Token是IBinder这样的一个接口,
IBinder是linux内核所支持的一套IPC进程间通讯的机制,
说明Activity之间的跳转是通过进程间的通信来完成的
intent:意图
requestCode:请求码
下面找到Instrumentation.java的源码
exeStartActivity方法中的核心部分如下:
getDefault方法返回的是一个IActivityManager的接口
其实实现这个接口的就是ActivityManager
其实finish方法也是使用的ActivityManager
为什么会用到IBinder,为什么activity跳转要用到IPC进程间通信机制?
因为Activity有四种LaunchMode,Activity可以存在于不同的进程之中
由于不能确保Activity是否属于同一进程,所以需要使用IPC进程间通信机制
比如点击按钮,开启了一个拨号,属于不同进程之间的切换
IntentFilter
当Intent在组件间传递时,组件如果想告知Android系统自己能够响应和处理哪些Intent,那么就需要用到IntentFilter对象
IntentFilter对象负责过滤掉组件无法响应和处理的Intent,只将自己关心的Intent接收进来进行处理
IntentFilter实行“白名单”管理,即只列出组件乐意接受的Intent,但IntentFilter只会过滤隐式Intent,显式的Intent会直接传送到目标组件
自定义一个IntentFilter
<activity ... >
<intent-filter>
<action android:name="com.test.demo.MyAction" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter></activity>
然后再activity跳转时
Intent intent = new Intent();
intent.setAction("com.test.demo.MyAction");
startActivity(intent);
需要注意的是,intent-filter中的category标签是必须要设置的
如果要传递Data
Intent intent = new Intent();
intent.setAction("com.test.demo.MyAction");
intent.setData(Uri.parase("http://www.116329.com"));
startActivity(intent);
那么在intent-filter中
<activity ... >
<intent-filter>
<action android:name="com.test.demo.MyAction" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
</intent-filter>
</activity>
这里的data表示只有以为http前缀的Data才会被接收
接收时
Intent intent = this.getIntent();
if(intent != null){
String dataString = intent.getDataString();
}
注意,如果有重复的action的话,会弹出选框以选择
就像打开文件时跳出不同程序以供打开一样
拨打电话时的代码
Intent intent = new Intent();
intent.setAction(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:12345678901"));
startActivity(intent);
Activity在MVC中的定位
M —— Window
V —— xml、UI
C —— Activity
Android SDK自带小工具HierarchyViewer
在Android SDK目录下tools目录中
能够查看程序View的层级关系
但是好像需要手机root才行(因为我的手机不能显示,但可以用模拟器)
经过查资料:出于安全考虑,Hierarchy Viewer只能连接Android开发版手机或是模拟器
商业手机可以通过某些手段开启查看,请参考http://www.dup2.org/node/1538
Activity生命周期
注意事项1:
一个Activity跳转时,首先是先暂停自己的activity(onPause),然后加载下一个activity(onStart,onCreated),
等下一个activity加载完毕以后,在停止自己的activity(onStop)
注意事项2:
在一个EditText中输入一些文字,切换到home,然后杀死这个进程,再返回程序发现输入的文字还在,但程序确实重新onStart了
这就说明有一套状态的保存于恢复机制
我们可以看一下onSaveInstanceState()这个函数
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
它在activity即将变为不可见的时候都会保存一个Bundle类型的数据(类似hash)
然后我们就明白onCreate()方法里的参数是干什么用的了
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
就是传入了那个保存了之前状态的Bundle
同理onRestoreInstanceState()也是一样
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
说明:onRestoreInstanceState()较少被调用,只有有些程序被kill掉的又重新召回的时候才会被调用
因为有时候不会被调用,所以不建议使用
在onSaveInstanceState()方法中,保存数据采用如下方式:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("save string", editText.getText().toString());
}
在onCreate()中可以取出保存的值
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 由于未存值时未空,所以需要判断
if (savedInstanceState != null){
editText.setText(savedInstanceState.getString("save string"));
}
}
采用上面的保存与还原的方法,我们会发现自己保存的值没有被还原
原因是我们个人保存的数据与系统保存的数据key不相同
系统优先回复了自己的数据
解决方法有两种:
一种方法是屏蔽onSaveInstanceState()方法中的super.onSaveInstanceState(outState);方法,不让系统自动保存
另一中方法是将回复数据写到onRestoreInstaceState方法中去,但此种方法不推荐
设置改变ConfigurationChanges
(一)设置改变的时候回复一个大对象
onRetainNonConfigurationInstance与getLastNonConfigurationInstance配合
注意returnObject不要与Context对象关联,比如View,否则会引起内存泄露
使用方法
保存:
@Override
public Object onRetainCustomNonConfigurationInstance() {
final MyDataObject data = collectMyloadedData();
return data;
}
使用:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
final MyDataObject data = (MyDataObject)getLastCustomNonConfigurationInstance();
if (data == null){
data = loadMyData();
}
}
(二)
自己处理配置改变
配置改变:语言改变,横屏竖屏切换,输入法有效无效切换
常用到onConfigurationChanged()方法
在manifest中配置configureChanges,这个属性的英文解释如下:
Specify one or more configuration changes that the activity will handle itself
内容可以选多项,彼此之间用" | "隔开即可
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"
/>
使用时
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// check if the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE){
Toast.makeText(this, "landscape", Toast.LENGTH_LONG).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
Toast.makeText(this, "portrait", Toast.LENGTH_LONG).show();
}
}
Android任务栈TaskStack
多个Activity按顺序组成的一个业务逻辑
可以在Activity中获得task id,从而证明任务栈的机制
Log.i("task id", getTaskId());
Acitivity在Androidmanifest.xml中的标签说明
<activity android:allowTaskReparenting=["true" | "false"]
android:alwaysRetainTaskState=["true" | "false"]
android:clearTaskOnLaunch=["true" | "false"]
android:configChanges=["mcc", "mnc", "locale",
"touchscreen", "keyboard", "keyboardHidden",
"navigation", "screenLayout", "fontScale", "uiMode",
"orientation", "screenSize", "smallestScreenSize"]
android:enabled=["true" | "false"]
android:excludeFromRecents=["true" | "false"]
android:exported=["true" | "false"]
android:finishOnTaskLaunch=["true" | "false"]
android:hardwareAccelerated=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
android:launchMode=["multiple" | "singleTop" |
"singleTask" | "singleInstance"]
android:multiprocess=["true" | "false"]
android:name="string"
android:noHistory=["true" | "false"]
android:parentActivityName="string"
android:permission="string"
android:process="string"
android:screenOrientation=["unspecified" | "behind" |
"landscape" | "portrait" |
"reverseLandscape" | "reversePortrait" |
"sensorLandscape" | "sensorPortrait" |
"userLandscape" | "userPortrait" |
"sensor" | "fullSensor" | "nosensor" |
"user" | "fullUser" | "locked"]
android:stateNotNeeded=["true" | "false"]
android:taskAffinity="string"
android:theme="resource or theme"
android:uiOptions=["none" | "splitActionBarWhenNarrow"]
android:windowSoftInputMode=["stateUnspecified",
"stateUnchanged", "stateHidden",
"stateAlwaysHidden", "stateVisible",
"stateAlwaysVisible", "adjustUnspecified",
"adjustResize", "adjustPan"] >
. . .
</activity>
allowTaskReparenting 允许任务重定向
比如在自己的程序中有一个网页链接,点击打开浏览器打开了这个页面
默认情况下程序运行完毕,重新打开浏览器(新的任务)会默认回到主页面
而将allowTaskReparenting设为true时则会打开浏览器时页面依旧存在
alwaysRetainTaskState
如果activity被杀死了,从launch切换回去的时候也会回到之前切换出去时候的状态
clearTaskOnLaunch
从launch进入程序时,清除掉root acitvity以上的所有的acitvity
记得是从launch进入,而不是home切换回去
finishOnTaskLaunch
当用户再次载入该task时(在主页屏幕上选择该task),一个既存的activity实例是否应该被关闭(结束)
true表示会关闭,false表示不是,默认值为false
hardwareAccelerated 硬件加速
与open gl有关
multiprocess
activity的实例能否能够被载入到启动它的组件所在的进程里
true表示可以,false表示不行,默认值为false
通常,一个acitivty的新实例被载入到定义它的应用程序进程里
这样一来,该acitvity的所有实例运行在同一个进程里
然而,如果本标识设为true的话,activity的实例就能够运行在多个进程里
允许系统只要使用它时就生成实例(提供的权限允许的话)
某些几乎根本不需要或根本不合适
noHistory
当用户离开它且它不再显示在屏幕上时(比如切换出去的时候),
activity是否应该被从activity stack里移除并且结束掉(调用它的finish方法)
true表示应该结束,false表示不,默认值是false
excludeFromRecents
不再最近的任务历史列表中包含它
process
可以直接设为一个值,表示修改进程别名(DDMS中的进程名为默认的包名)
还可以使用“:”+字符串,有可能会在应用内创建一个新的进程
stateNotNeeded
不自动保存状态,比如切换时不再保存已经输入的文字
相当于onSaveInstanceState()中保存了null
export
是否允许被其他进程使用,一般都是允许的
windowsSoftInputMode
输入法状态
LaunchMode
一共有4种执行模式:
standard
singleTop
singleTask
singleInstance
当launchMode为standard
正常的模式,调用一次,创建一个新实例
退出时也按进栈的顺序依次出栈
当launchMode为singleTop
该activity不在栈顶时,仍然会创建新的实例
在栈顶时,请求启动该activity的Intent并没有触发创建新实例
而是触发了该activity的onNewIntent()方法
所以连续开启多个此种activity不会生成新的实例,按back时只需按一下就能返回上一个activity
当launchMode为singleTask
activity已经存在时,再次请求该activity回出发已经存在的实例,调用onNewIntent()
并且如果该activity所在的任务栈 位于该activity上方的所有activity都会被销毁
当launchMode为singleInstance
独立成任务栈,并且有且仅有自己,而且始终保持一个实例
离栈的时候,先退当前的任务栈,再退其他的task
Android提供的专有Activity
MapActivity
ListActivity
ExpandableListActivity
TabActivity