Android基础知识
1 五种布局
Android有五种布局,分别为FrameLayout 、 LinearLayout 、 AbsoluteLayout 、 RelativeLayout 、 TableLayout,且它们全都继承自ViewGroup
。
FrameLayout(框架布局)
此布局是五种布局中最简单的布局,Android中并没有对child view的摆布进行控制,这个布局中所有的控件都会默认出现在视图的左上角,我们可以使用
android:layout_margin
,android:layout_gravity
等属性去控制子控件相对布局的位置。LinearLayout(线性布局)
一行(或一列)只控制一个控件的线性布局,所以当有很多控件需要在一个界面中列出时,可以用LinearLayout布局。 此布局有一个需要格外注意的属性:
android:orientation="horizontal|vertical"
。
1 | 当android:orientation="horizontal"时,说明你希望将水平方向的布局交给LinearLayout,其子元素的android:layout_gravity="right|left" 等控制水平方向的gravity值都是被忽略的,此时LinearLayout中的子元素都是默认的按照水平从左向右来排,我们可以用android:layout_gravity="top|bottom"等gravity值来控制垂直展示。 |
AbsoluteLayout(绝对布局)
可以放置多个控件,并且可以自己定义控件的x,y位置。
RelativeLayout(相对布局)
这个布局也是相对自由的布局,Android 对该布局的child view的水平layout和 垂直layout做了解析,由此我们可以FrameLayout的基础上使用标签或者Java代码对
垂直方向
以及水平方向
布局中的views进行任意的控制。
1 | //相关属性 |
TableLayout(表格布局)
将子元素的位置
分配到行或列
中,一个TableLayout由许多的TableRow组成。
2 Activty生命周期
- 启动Activity: onCreate()—>onStart()—>onResume(),Activity进入运行状态。
- Activity退居后台: 当前Activity转到新的Activity界面或按Home键回到主屏: onPause()—>onStop(),进入停滞状态。
- Activity返回前台: onRestart()—>
onStart()
—>onResume(),再次回到运行状态。 - Activity退居后台,且系统内存不足, 系统会杀死这个后台状态的Activity(此时这个Activity引用仍然处在任务栈中,只是这个时候引用指向的对象已经为null),若再次回到这个Activity,则会走onCreate()–>onStart()—>onResume()(将重新走一次Activity的初始化生命周期)
- 锁屏:
onPause()->onStop()
- 解锁:
onStart()->onResume()
- 更多流程分支,请参照以下生命周期流程图
3 通过Acitivty的xml标签来改变任务栈的默认行为
使用android:launchMode="standard|singleInstance|singleTask|singleTop"
来控制Acivity任务栈。
任务栈是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其onDestory()
方法。如果栈内没有Activity,那么系统就会回收这个栈,每个APP默认只有一个栈,以APP的包名来命名。
standard
标准模式,每次启动Activity都会创建一个新的Activity实例,并且将其压入任务栈栈顶,而不管这个Activity是否已经存在。Activity的启动三回调(onCreate()->onStart()->onResume())都会执行。
singleTop
栈顶复用模式.这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,所以它的启动三回调就不会执行,同时Activity的
onNewIntent()
方法会被回调.如果Activity已经存在但是不在栈顶,那么作用与standard模式一样。singleTask
栈内复用模式.创建这样的Activity的时候,系统会先确认它所需任务栈已经创建,否则先创建任务栈.然后放入Activity,如果栈中已经有一个Activity实例,那么这个Activity就会被调到栈顶,
onNewIntent()
,并且singleTask会清理在当前Activity上面的所有Activity.(clear top)。singleInstance
加强版的singleTask模式,这种模式的Activity只能
单独
位于一个任务栈内,由于栈内复用的特性,后续请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了。
Activity的堆栈管理以ActivityRecord
为单位,所有的ActivityRecord都放在一个List里面.可以认为一个ActivityRecord就是一个Activity栈。
3 Activity缓存方法
有a、b两个Activity,当从a进入b之后一段时间,可能系统会把a回收,这时候按back,执行的不是a的onRestart
而是onCreate方法,a被重新创建一次,这时a中的临时数据和状态可能就丢失了。
可以用Activity中的onSaveInstanceState()
回调方法保存临时数据和状态,这个方法一定会在活动被回收之前调用。方法中有一个Bundle参数,putString()、putInt()等方法需要传入两个参数,一个键一个值。数据保存之后会在onCreate中恢复,onCreate也有一个Bundle类型的参数。
1 | //示例代码 |
3.1 onSaveInstanceState (Bundle outState)
当某个activity变得“容易”被系统销毁时,该activity的onSaveInstanceState就会被执行,除非该activity是被用户主动销毁的,例如当用户按BACK键的时候。
注意上面的双引号,何为“容易”?言下之意就是该activity还没有被销毁,而仅仅是一种可能性。这种可能性有哪些?通过重写一个activity的所有生命周期的onXXX方法,包括onSaveInstanceState和onRestoreInstanceState方法,我们可以清楚地知道当某个activity(假定为activity A)显示在当前task的最上层时,其onSaveInstanceState方法会在什么时候被执行,有这么几种情况:
当用户按下HOME键时
这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则
长按HOME键,选择运行其他的程序时
按下电源按键(关闭屏幕显示)时
从activity A中启动一个新的activity时
屏幕方向切换时,例如从竖屏切换到横屏时
(如果不指定configchange属性) 在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState一定会被执行。
总而言之
,onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。另外,需要注意的几点
:
布局中的每一个View
默认
实现了onSaveInstanceState()方法,这样的话,这个UI的任何改变都会自动地存储和在activity重新创建的时候自动地恢复。但是这种情况只有在你为这个UI提供了唯一的ID之后才起作用,如果没有提供ID,app将不会存储它的状态。由于默认的onSaveInstanceState()方法的实现帮助UI存储它的状态,所以如果你需要覆盖这个方法去存储额外的状态信息,你应该在执行任何代码之前都
调用父类的onSaveInstanceState()方法(super.onSaveInstanceState())
。 既然有现成的可用,那么我们到底还要不要自己实现onSaveInstanceState()?这得看情况了,如果你自己的派生类中有变量影响到UI,或你程序的行为,当然就要把这个变量也保存了,那么就需要自己实现,否则就不需要。由于onSaveInstanceState()方法调用的不确定性,你应该只使用这个方法去记录activity的瞬间状态(UI的状态)。不应该用这个方法去存储持久化数据。当用户离开这个activity的时候应该在
onPause()方法
中存储持久化数据(例如应该被存储到数据库中的数据)。onSaveInstanceState()如果被调用,这个方法会
在onStop()前被触发
,但系统并不保证是否在onPause()之前或者之后触发。
3.2 onRestoreInstanceState (Bundle outState)
至于onRestoreInstanceState方法,需要注意的是,onSaveInstanceState方法和onRestoreInstanceState方法“不一定”是成对的被调用的,(本人注:我昨晚调试时就发现原来不一定成对被调用的!)
onRestoreInstanceState被调用的前提是,activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行
另外,onRestoreInstanceState的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原。 还有onRestoreInstanceState在onstart之后执行。 至于这两个函数的使用,给出示范代码(留意自定义代码在调用super的前或后):
1 | @Override |
4 Fragment的生命周期和activity的关系
为什么在Service中创建子线程而不是Activity中❓
这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。
Intent的使用方法,可以传递哪些数据类型❓
通过查询Intent/Bundle的API文档,我们可以获知,Intent/Bundle支持传递基本类型的数据和基本类型的数组数据,以及String/CharSequence类型的数据和String/CharSequence类型的数组数据。而对于其它类型的数据貌似无能为力,其实不然,我们可以在Intent/Bundle的API中看到Intent/Bundle还可以传递Parcelable(包裹化,邮包)和Serializable(序列化)类型的数据,以及它们的数组/列表数据。
所以要让非基本类型和非String/CharSequence类型的数据通过Intent/Bundle来进行传输,我们就需要在数据类型中实现Parcelable接口或是Serializable接口。