最近想着把安卓开发的知识重新拾起。本科的时候学过一个月的安卓开发,可惜没有坚持下去,现在常常后悔,如果当时好好打基础,现在逆向后看到一些代码肯定不会觉得太陌生。我觉着逆向想要学好,开发也是基本功。遂决定每天开发与逆向同时学习。目前参考书籍为《Android第一行代码》,先打好基础再进阶。
今天要学习的是安卓里面的Activity,作为Android四大组件之一,在逆向分析恶意软件的时候,在分析完Application入口点后,第一个要找的就是MainActivity主界面,在此窥探程序的执行逻辑。Activity与Application不同,一个App可以包含多个Activity,Activity的生命周期与活动自身有关,而Application生命周期与App有关,所以这也是为什么一些系统变量或者全局变量的初始化在Application中完成。
1.Activity的四种活动状态包括:
运行状态:当活动处于返回栈的顶端时,活动处于运行状态
暂停状态:当活动不处于栈顶,但是依然可见,例如对话框
停止状态:当活动不处于栈顶,并且完全不可见
销毁状态:活动不在栈中
这几个状态又和Activity的生命周期相关,首先贴出一张生命周期图:参考链接(https://www.runoob.com)
- onCreate():活动被创建时调用的方法,方法中完成对控件布局之类的初始化,oncreate方法的参数为上次活动被异常销毁时保存的状态信息。
- onRestart():Activity被重新启动,例如用户按Home键切换到桌面或者用户打开了新的Activity,然后用户又回到这个Activity的时候会被调用。
- onStart():活动被创建之后正在启动,此时活动可见,但是不处于前台无法与用户完成交互。
- onResume():活动启动完成并且获得焦点,此时活动可见,且处于前台,可以与用户完成交互。
- onPause():活动暂停状态,表示活动正在停止。这里分两种情况。一种是类似于对话框那种,虽然活动不再处于栈顶但是依旧可见,这种情况下不会连带执行onStop,另一种是启动了另一个Activity并完全覆盖,这种情况一般会连带执行onStop。onPause时可以做一些数据保存、停止动画等工作。
- onStop():活动即将停止,此时可做一些稍微重量级的回收工作,例如取消网络连接等,但是不能太耗时。在此如果回退前一个Activity,会执行onRestart方法之后onStart->onResume重新获得焦点。
- onDestory:活动即将销毁,可以做一些些回收工作和资源释放。
也就是onStart(可见不可操作)->onResume(可见可操作)->onPause(可见不可操作)->onStop(不可见不可操作)
在上图的基础上讨论一些常见的应用场景
Q:第一次启动Activity A,回调哪些方法?
A:Activity A的onCreate()->onStart()->onResume()
Q:已经启动Activity A,现在要启动Activity B,会回调哪些方法?
A:ActivityA的onPause()->ActivityB的onCreate()->onStart()->onResume(),之后就按照上面介绍onStop()中的那样分为两种情况,如果ActivityA完全不可见了,会连带执行ActivityA->onStop(),否则对话框那种的不会连带执行ActivityA->onStop()。
Q:第二个Q&A中如果再返回到Activity A
A:Activity B的onPause()->Activity A的onRestart()->onStart()->onResume(),假设这里B已经完全不可见,那么接下来会执行Activity B的onStop()->onDestory()。
Q:Activity A弹出了对话框Activity B
A:如第二个Q&A中所述,ActivityA的onPause()->ActivityB的onCreate()->onStart()->onResume()。
Q:Activity A弹出了对话框Activity B后,再按Home键或电源键关闭屏幕,再回到Activity B或者点亮屏幕
A:按Home键或电源键关闭屏幕后,此时Activity A和Activity B都不可见,那么会执行Acitvity B的onPause()->onStop(),接着执行Activity A的onStop()。再回到Activity B或者点亮屏幕,Activity B重新获得焦点,那么首先会执行Activity B的onRestart()->onStart(),接着执行Activity A的onRestart()->onStart(),Activity B的onResume()。
Q:关闭对话框Activity B
A:Activity B的onPause()->Activity A的onResume() -> Activity B的onStop() ->onDestory()。
2.Activity生命周期中onCreate方法的两种重构方法:
一种是常见的
另一种是:
这需要在XML文件中声明Activity属性为android:persistableMode=“persistAcrossReboots”
然后我们的Activity就拥有了持久化的能力了,一般我们会搭配另外两个方法来使用:onSaveInstanceState和onRestoreInstanceState
这两种分别是用来保存和恢复活动临时数据的。
onSaveInstanceState在活动可能被销毁时并有机会重新显示时调用,例如点击home键回到主页、长按后选择运行其他程序、按下电源键关闭屏幕、系统配置改变例如横竖屏切换、启动新的Activity、系统内存不足活动回收等等时会被调用用来保存临时数据,该方法调用在onStop之前。这点与onPause不同,onPause用来保存持久性数据。onRestoreInstanceState在活动确实被销毁重建时调用以恢复临时性数据,一般是在onStart()和onResume()之间执行。
《Android开发艺术探索》里提到:
如果是系统重建引起的活动的重建,那么活动在被销毁前具体来说是onStop之前会调用onSaveInstance来保存临时性数据,并且系统也自动的保存了当前活动的视图结构例如文本框输入的数据等等。活动重建之后会在onCreate或者onRestoreINstanceState中恢复保存的临时性数据。不同之处在于onCreate中需要判断Bundle实例是否为空。
3.横竖屏切换与状态保存的问题
系统配置改变例如横竖屏切换时会导致Activity重建,也就是切换时Activity会被onDestory然后重新onCreate一直到onResume。
解决的办法常用的有几个,一是横竖屏时onCreate方法中加载不同的布局,二是通过上述onSaveInstanceState方法来保存数据。
当然为了避免Activity被重建,也可以为Activity指定属性android:configChanges=“orientation|screenSize”,这样当系统配置改变。 Activity不会被重建,而是调用onConfigurationChanged重写方法。
4.Activity的四种启动模式
1)standrad默认模式
我们知道, Activity使用返回栈来管理活动的,默认模式就是启动新的入栈,回退出栈,不管栈中有没有重复
2)singleTop模式
如果要启动的Activity已经位于任务栈的栈顶,就不会重新创建而是使用栈顶的活动实例,并且会调用该实例的onNewIntent()方法将Intent对象传递到这个实例中。当然如果启动的活动不在栈顶但在栈中,仍然会创建新的活动。通常应用于防止点击多次某活动启动多次该活动实例的情景例如推送通知栏。
3)singleTask模式
解决了singleTop中栈中有实例仍会创建的问题,如果栈中有,就弹出上面所有的活动实例,并回调 onNewIntent(intent)方法。通常用于只允许有一个实例的场景例如主页面、购物界面,确认订单界面,付款界面。singleTask模式下可以通过TaskAffinity指定所需要任务栈的名字,这种情况和singleInstance相似,若指定的任务栈不存在会新建任务栈。任务栈分为前台与后台,后台任务栈中的活动处于暂停状态。
4)singleInstance
具有此模式的Activity只能单独位于一个任务栈中,且此任务栈中只有唯一一个实例, 当再次启动该activity的实例时,会重用已存在的任务和实例。并且会调用这个实例的onNewIntent()方法,将Intent对象传递到该实例中,singleInstance一般应用于系统Launcher、锁屏键、来电显示等系统应用。
为活动设置启动模式有两种方法:
- 在AndroidManifest中为Activity指定启动模式 android:launchMode=“singleTask”
- 通过在Intent中设置标记位来指定Activity的启动模式intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
两种方式的区别:优先级上第二种的优先级要高于第一种,第一种方式无法直接为Activity设置FLAG_ACTIVITY_CLEAR_TOP标识,第二种方式无法为Activity指定singleInstance模式
5.Activity的启动过程
6.IntentFilter匹配规则
(1) 一个intent只有同时完全匹配某个Activity intent-filter中的action、category、data,才能成功启动该Activity。
(2)一个Activity可以有多组intent-filter,一个intent只要能成功匹配一组intent-filter,就可以成功启动该Activity。
action匹配:
要求Intent中的action 存在且必须和intent-filter中的其中一个 action相同。action的匹配区分大小写。
category匹配:
intent中的category可以不存在,这是因为此时系统给该Activity 默认加上了
< category android:name=“android.intent.category.DEAFAULT” />属性值。
除默认情况外,假如Intent有其他的category,则要求每个category和intent-filter中的任何一个category 相同。
data匹配:
data由两部分构成:mimeType和URI,前者指的是媒体类型,后者指一种资源通用标识符。
如果Activity的intent-filter中有定义data,那么Intent中也必须也要定义data。在Intent定义data时通过intent.setDataAndType(Uri data, String type)方法进行匹配。参数前者是Uri对象,后者是mimeType类型。
可以通过PackageManager的resolveActivity方法或者Intent的resolveActivity方法判断是否有Activity匹配该隐式Intent以规避错误。
7.Demo
Demo1: Menu
顺便在此学习一下menu的用法, activity的布局如下(Kotlin与Java相同):
加载Menu的方式有两种,一种是直接通过编写菜单XML文件,然后调用: getMenuInflater().inflate(R.menu.menu_main, menu)加载菜单,或者通过代码动态添加。
先看静态方式:
首先在res目录创建menu文件夹:
menu文件夹中新建menu.xml, 然后通过item方式来添加子项:
然后重写 onCreateOptionsMenu方法,在方法中调用getMenuInflater().inflate(R.menu.menu_main, menu)显示菜单,重写onOptionsItemSelected方法监听事件:
如下图所示:
Kotlin版,效果相同:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu,menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
R.id.add -> Toast.makeText(this,"Add",Toast.LENGTH_SHORT).show()
R.id.remove -> Toast.makeText(this,"Remove",Toast.LENGTH_SHORT).show()
}
return true
}
}
再看动态方式:
在onCreateOptionsMenu中通过add方法添加子项。效果如下:
Demo2: Intent传递数据
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button1"/>
</LinearLayout>
activity_second.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondActivity">
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button2"/>
</LinearLayout>
MainActivity:通过putExtra传递信息
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button1.setOnClickListener{
val data = "Hello world"
val intent = Intent(this,SecondActivity::class.java)
intent.putExtra("key",data)
startActivity(intent)
}
}
}
SecondActivity:通过getStringExtra得到传递的信息
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val value = intent.getStringExtra("key");
Toast.makeText(this,"value is $value",Toast.LENGTH_SHORT).show();
}
}
效果如下,点击button1: