Activity的内容比较多,这是剩下的第二部分。
多个Activity
一般应用会有多个Activity,下面来进行多个跳转。
我们还是和上一章一样的方法创建一个新的Activity,我们取名SeconeActivity。如图:
点击Finish。
显式Intent调用
我们发现Mainfest里已经存在了SecondActivity,我们直接在FirstActivity里去通过Intent来显示我们的SecondActivity,代码如下:
binding.button1.setOnClickListener(){
val intent = Intent(this,SecondActivity::class.java)
startActivity(intent)
Toast.makeText(this,"点击了按钮!",Toast.LENGTH_SHORT).show()
}
然后我们修改SeconeActivity.kt
让它的按钮点击返回First界面。
class SecondActivity : AppCompatActivity() {
protected lateinit var binding : ActivitySecondBinding //定义全局的,方便别的函数使用
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySecondBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view.rootView)
binding.button2.setOnClickListener(){
finish()
}
}
}
点击后,我们还是调用finish,上一章讲过了,相当于Back了。
隐式Intent调用
我们在项目Manifest中添加intent-filter
<activity
android:name=".SecondActivity"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.SEC" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
然后调用方式改为
binding.button1.setOnClickListener(){
//val intent = Intent(this,SecondActivity::class.java)
val intent = Intent("android.intent.action.SEC")
startActivity(intent)
Toast.makeText(this,"点击了隐式Intent!",Toast.LENGTH_SHORT).show()
}
多个category
每个Intent中只能指定一个Action,可以多个category.
binding.button1.setOnClickListener(){
//val intent = Intent(this,SecondActivity::class.java)
val intent = Intent("android.intent.action.SEC")
intent.addCategory("android.intent.category.My")
startActivity(intent)
Toast.makeText(this,"点击了隐式Intent!",Toast.LENGTH_SHORT).show()
}
我们通过addCategory函数添加了android.intent.category.My,相应在manifest里也要添加。
添加多个的意义是什么呢 ?书中没有明示,可能后面才有详细讲解。
Intent调用别的应用
例如你可以需要跳转浏览器,你可以
binding.button1.setOnClickListener(){
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://csdn.net")
startActivity(intent)
Toast.makeText(this,"点击了隐式Intent!",Toast.LENGTH_SHORT).show()
}
让Activity能够响应http请求
上面的例子跳转了浏览器,这里我们自己做一个接受网页的Activity。
我们创建一个新的ThirdActivity。
manifest里
<activity
android:name=".ThirdActivity"
android:exported="false"
tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" />
</intent-filter>
</activity>
注意这里的intent.action.VIEW和android:scheme=“https”
然后运行后点击button1,应该会弹起浏览器选择界面,里面就有我们的NewActivity应用。
我的又没成功,为什么拉不起来呢。
这里虽然可以选择我们的应用,但是没有实质的显示网页的功能。
电话调用
我们继续修改firstActivity。
binding.button1.setOnClickListener(){
val intent = Intent(Intent.ACTION_DIAL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
Toast.makeText(this,"点击了隐式Intent!",Toast.LENGTH_SHORT).show()
}
点击后会弹起电话拨号,并输入10086。
传递数据
假设从First传递数据到Second,可以这样,在First中
val intent = Intent(this,SecondActivity::class.java)
val data = "send data !"
intent.putExtra("extra_data",data)
startActivity(intent)
在Second中
val extraData = intent.getStringExtra("extra_data")
Log.d("test" , "extraData : $extraData")
运行后点击按钮,就切换到了SecondActivity,并且log输出了 “send data !”
返回数据
在First里
import androidx.activity.result.contract.ActivityResultContracts
private val getResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{
if(it.resultCode == Activity.RESULT_OK)
{
val value = it.data?.getStringExtra("inputs")
Log.d("test",value.toString())
}
}
binding.button1.setOnClickListener(){
binding.button1.setOnClickListener(){
val intent = Intent(this,SecondActivity::class.java)
getResult.launch(intent)
}
再Sec里
binding.button2.setOnClickListener()
{
onBackPressed()
}
override fun onBackPressed() {
val intent = Intent()
intent.putExtra("inputs","xxxxxx223")
setResult(RESULT_OK,intent)
Log.d("test","button 2 click - onBackPressed")
finish()
}
书上的StartActivityForResult方法过时了。
上面的代码是替换方案,使用registerForActivityResult。
Sec里返回用的是覆写了返回事件。
Activity生命周期
返回栈
每个Activity都放入栈里,最新的在最上面,每退出一个就会返回下面一个。
Activity状态
1,运行状态,位于栈顶部。
2,暂停状态,不是顶部,但是仍然可见。
3,停止状态,不是顶部,也不可见。
4,销毁状态,从栈中移除。
生存期
OnCreate , 第一次被创建
OnStart , 不可见变可见
OnResume,准备号和用户交互,一定位于栈顶,且是运行状态
OnPause,准备去启动或者回复一个Activity的时候调用
OnStop,完全不可见的时候调用。
OnDestroy,销毁之前调用
OnRestart,由停止状态变为运行状态前调用。
被回收了的处理
假如在Activity A的基础上启动了B,系统内存不足把A回收了,那么返回的时候还是能回到A的,这时候A会执行onCreate方法。要注意你的临时数据是全部都丢失了。因此提供了一个onSaveInstanceState的回调方法来解决问题。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//setContentView(R.layout.first_layout)
binding = FirstLayoutBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view.rootView)
if(savedInstanceState != null)
{
val d = savedInstanceState.getString("data_recover")
Log.d("test","data_recover$d")
}
。。。。。
。。。。。
}
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
super.onSaveInstanceState(outState, outPersistentState)
val tempData = "xxxxxxxxx"
outState.putString("data_recover",tempData)
}
覆写onSaveInstanceState函数保存数据,然后再Oncreate函数里进行恢复。
启动模式
启动模式可以通过manifest修改launchMode来修改:
<activity
android:name=".FirstActivity"
android:launchMode="singleTop"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
下面是4种启动模式特点
standard
单个Activity可以启动多次,也会back多次的模式。
singleTop
只会启动一个,如果不在栈顶的话,启动的时候还是会进入onCreate,它会创建新的,把老的关闭掉,如果back,它最下面的还是会进入一次。
singleTask
如果不在顶部,调用的时候会把其他Activity全部请出栈。
singleInstance
会单独一个返回栈,解决共享Activity实例的问题。
要熟练掌握上面的模式,需要在工作中大量的使用,下面介绍几个常用的实践。
实践
当前是哪一个Activity
当调试别人代码的时候,可能需要只是简单的增加一个组件,但是无法从一堆Activity里找到对应的。
我们可以创建一个基类BaseActivity,让其他Activity都继承它。
package com.example.newactivity
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
open class BaseActivity : AppCompatActivity() {
override fun onCreate(saveInstanceState: Bundle?)
{
super.onCreate(saveInstanceState)
Log.d("BaseActivity",javaClass.simpleName)
}
}
退出程序
如果你打开了多层的Activity,一直Back好几次才能退出,如果只是按Home,那么只能返回。
我们可以制作一个单例类ActivityCollector,通过ArrayList来存储Activity,关闭的时候在基类里的onDestroy函数里通过ActivityCollector.removeActivity(this)移除。在单例类中可以写一个removeAll,遍历List,让他们都finish()。
最后还可以加上杀进程操作。
android.os.Process.killProcess(android.os.Process.myPid())
启动Activity最佳写法
前面的文章中提到需要通过Intent,调用putExtra传递参数。
还有一种方法,假如要启动SecondActivity,你可以直接在SecondActivity里添加
companion object{
fun MyStart(context: Context, data1:String, data2:String)
{
val intent = Intent(context,SecondActivity::class.java)
intent.putExtra("parm1",data1)
intent.putExtra("parm2",data2)
context.startActivity(intent)
}
}
在启动页面里直接
SecondActivity.MyStart(this,"mydata1","mydata2")
companion是一个新的语法结构,里面的方法都可以使用类似静态方法的方式调用。
标准函数
Kotilin标准函数指的是Standard.kt中定义的函数,下面讲解几个常用的。
with
和c#的with类似。
例如
val builder = StringBuilder()
builder.append("xxx1")
val result = builder.toString()
println(result)
val b = StringBuilder()
val result2 = with(b){
append("yyyyy1")
toString()
}
println(result2)
println("over")
2022-08-03 14:40:56.865 8116-8116/com.example.newactivity I/System.out: xxx1
2022-08-03 14:40:56.866 8116-8116/com.example.newactivity I/System.out: yyyyy1
2022-08-03 14:40:56.866 8116-8116/com.example.newactivity I/System.out: over
run
run函数和with很类似,必须是一个对象调用,其他没区别。
val c = StringBuilder()
val result3 = c.run{
append("cccc1")
toString()
}
println(result3)
apply
apply函数和run类似,不过它只能改变对象自己的数据,像上面的toString是不能有的(书上是这么说的),但是我测试下来和run没有什么区别,为什么呢?
val d = StringBuilder()
val result4 = d.apply{
append("ddd1")
toString()
}
println(result4)
调试发现apply返回的只是对象本身,并不是字符串。所以那个toString应该没有任何意义,但是又不会报错。
静态方法
和C#不一样,在函数上加上static关键字就可以了。Kotilin有其他的方法,那就是单例。
object单例
package com.example.newactivity
object Comm {
fun comm1()
{
println("fun - comm1")
}
}
Comm.comm1()
com.example.newactivity I/System.out: fun - comm1
是不是挺简单,但是这样会导致Comm这个单例类里面的所有方法都可以直接使用。
我们可以继续使用上面的伴生类(companion object)。把类的object改为class,如下:
伴生类
package com.example.newactivity
class Comm {
fun comm1()
{
println("fun - comm1")
}
companion object
{
fun comm2()
{
println("fun - comm2")
}
}
}
我们发现comm1已经无法调用了。
上面的方法都是模仿了静态的方法,他们并不是真正意义的静态方法,下面介绍真正的。
注解静态方法
package com.example.newactivity
class Comm {
fun comm1()
{
println("fun - comm1")
}
companion object
{
@JvmStatic
fun comm2()
{
println("fun - comm2")
}
}
}
只要加上@JvmStatic注解就可以了。它只能加在单例或者伴生方法中。那么现在不管是Kotilin还是Java都可以使用Comm.comm2()来调用了。
顶层方法
还有一种,就是在kt文件中的直接书写的函数名,在整个项目中都是静态的,可以直接调用。
package com.example.newactivity
fun comm0()
{
println("fun - comm0")
}
class Comm {
fun comm1()
{
println("fun - comm1")
}
companion object
{
@JvmStatic
fun comm2()
{
println("fun - comm2")
}
}
}
调用:
comm0()
Comm.comm2()
输出:
I/System.out: fun - comm0
I/System.out: fun - comm2
但是这种Java代码中是找不到这个方法的。这个类是Comm.kt,其实被编译成了Commkt.class的Java类。
我Java中我们可以使用CommKt.comm0()的写法来调用。
最后Activity的基础就到这里了。文中有几处红色字体标注的不太明白,后面有机会再回头看看。
参考
《第一行代码》Android第三版
Kotilin Function