Android 自学记录 最后更新于2020.9.13


自学记录,书籍参考《第一行代码Android》郭霖

Activity相关

手动创建布局

以项目名为ActivityTest为例,项目布局选择Add no Activity
1、在app/src/main/java/com.example.activitytest包右键New->Activity->Empty创建一个新的布局(FirstActivity),取消勾选Generate Layout File(自动创建布局文件)和Launcher Activity(设置为主activity),点击Finish完成创建
2、在app/src/main/res目录下新建一个layout目录,右键layout->
New->Layout resource file,输入布局文件名(first_layout),根目录默认,点击ok完成创建
3、在FirstAcitvity.kt中的override fun onCreate()方法中加入如下代码

setContentView(R.layout.first_layout)

4、在AndroidManifest.xml文件中注册,在< activity >元素中插入如下代码

<activity android:name=".FirstActivity" android:label="This is FirstActivity">
	<intent-filter>
		<action android:name="android.intent.action.MAIN"/>
		<category android:name="android.intent.category.LAUNCHER"/>
	</intent-filter>
</activity>

其中,".FirstActivity"由于前面有package声明而省略前面的部分,android:label部分会在该activity加载后出现在标题部分,< intent-filter >元素中的代码是在进行主Activity配置

创建按钮组件

继续使用上文的项目,在first_layout.xml中此时已经存在< LinearLayout >元素,此时在其中可插入如下代码

<Button
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:id="@+id/Button1"
	android:text="Button1"
/>

其中,增加id使用@+id/id_name 如果是引用当前已有id则使用@id/id_name,match_parent参数代表布局的宽度对齐父元素,wrap_content参数代表布局的高度刚好能够包含当前内容

设置Toast提醒

Toast是Android提供的提醒方式,在程序中可以发送一条快速的信息提示并自动消失
继续使用上文的项目,在FirstAcitvity.kt中的override fun onCreate()方法中加入如下代码

val button1:Button=findViewById(R.id.Button1)
button1.setOnClickListener { 
	Toast.makeText(this,"you clicked Button1",Toast.LENGTH_SHORT).show()
}

在Avtivity中,通过findViewByID()方法获取在布局文件中的元素,传入R.id.Button1后获取了按钮的实例,但是由于推导机制无法自动推导控件类型,所以需要显式声明为Button类型

获得实例后,通过.setOnClickListener{}为按钮注册一个监听器,当点击按钮时会执行监听器中的onClick()方法即后面的{}中内容

makeText()方法需要三个参数,第一个参数是Context,是Toast要求的上下文,由于Activity本身是一个Context对象,因此传入this即可,第二个参数是内容,第三个参数是显示的时长,默认提供两个内置常量Toast.LENGTH_SHORT/LONG

在有多个组件的时候,若是每个组件都进行findViewById()的声明过于繁琐,因此有插件提供了支持(需要AS代码补全的功能协助),此时可以直接通过id使用该对象,即可删去上述代码的第一行

menu

菜单展示
1、在res目录下新建一个menu目录,再在该目录下新建一个Menu resource file(命名为main)

2、在< menu >元素中插入以下代码

<item
	android:id="@+id/add_item"
	android:title="add"
/>
<item
	android:id="@+id/remove_item"
	android:title="Remove"
/>

上述代码创建了两个菜单项,分别分配了唯一的id和指定的名称

3、回到FirstActivity.kt,重写onCreateOptionsMenu()方法(ctrl+o快速选择重写方法),重写代码如下

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
	menuInflater.inflate(R.menu.main,menu)
	return true
}

由于在Kotlin中调用Java Bean的语法结构的方法时,被简化为可以直接使用对象引用做到getter/setter的效果,因此menuInflater实际上是调用了父类的getMenuInflater()方法获得对象,再使用该对象的inflate()方法,该方法接受两个参数,第一个参数是指定资源文件进行菜单创建,第二个参数是用于指定菜单项将添加到哪一个Menu对象当中

4、上述步骤完成了菜单的显示,但菜单还应该有相应的能力,因此继续在FirstActivity.kt继续编写代码如下

override fun onOptionsItemSelected(item: MenuItem): Boolean {
	when(item.itemId){
		R.id.add_item->Toast.makeText(this,"You clicked Add.",Toast.LENGTH_SHORT).show()
		R.id.remove_item->Toast.makeText(this,"You clicked Remove.",Toast.LENGTH_SHORT).show()
	}
	return true
}

在onOptionsItemSelected()方法中通过调用item.itemId判断点击的是哪一个菜单项(实际上也是调用了getItemId()

删除布局

使用finish()做到和手机的back键一样的效果,退出当前的Activity
除此之外,可以使用finishActivity()结束之前启动的活动,finish使用样例如下
在first_layout.xml中创建一个Button元素命名为Exit的过程略,在FirstActivity.kt中加入如下代码即可

exit.setOnClickListener {
	finish()
}

用Intent启动Activity

为了完成多Activity的应用,需要在Activity之间进行跳转,此时就用到了Intent

继续上面的项目,在activitytest包中创建SecondActivity,这次勾选Generate Layout File选项,让AS进行创建,创建后会自动生成SecondActivity.kt和layout_second.xml,为了方便接下来的操作(实际创建的情况和书上的内容略有差异稍作调整),将layout_second.xml重命名为second_layout.xml(修改后SecondActivity.kt中的布局名自动发生了更改),然后将second_layout.xml的内容替换为LinearLayout布局(复制first_layout.xml的内容),并定义按钮Button2,检查AndroidManifest.xml时会看到AS自动添加了新的< activity >元素

完成创建后,接下来就需要考虑如何启动的问题,此时引入Intent的概念,Intent是Android各组件进行交互的重要方式,可以在不同组件之中传递数据,当前仅研究如何启动Activity,大致上可以将Intent分为两种:显式Intent和隐式Intent

显式Intent

Intent有多个构造函数重载,其中之一是Intent(Context packageContext,Class<?> cls),第一个参数Context要求提供一个启动Activity的上下文,第二个参数用于指定启动的Activity,该方法显示了该Intent的“意图”,而其使用要搭配Activity类的startActivity()方法
修改Button1的代码如下

button1.setOnClickListener {
	val intent=Intent(this,SecondActivity::class.java)
		startActivity(intent)
	}

上述代码即在FirstActivity的基础上打开SecondActivity,在不做其他设置的情况下返回FirstActivity直接使用手机的back键即可

由于上述方式启动Activity的过程中,Intent的"意图"十分明显,因此被称为显式Intent
*仿照上文在SecondActivity中创建一个Exit Button,编写好finish()后发现是销毁了当前的Activity而不是直接退出App

隐式Intent

隐式的Intent并不明确的指出启动哪一个Activity,而是通过指定一系列抽象的action和category信息,交由系统进行分析并决定启动合适的Activity
所谓合适的Activity,就是可以响应对应Intent的Activity,而为了要让成为对应的Activity,需要通过< activity >标签下配置< intent-filter >对action和category进行指定,打开AndroidManifest.xml,在
SecondActivity的< activity >中添加如下代码

<intent-filter>
	<action android:name="com.example.activitytest.ACTION_START"/>
	<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

上述代码中< action >指明了当前Activity可以响应com.example.activitytest.ACTION_START这个action,而< category >则更精确指明当前的Activity中可能带有的category,二者均满足时这个Activity才能响应该Intent
而后修改Button1的代码为

button1.setOnClickListener {
	val intent=Intent("com.example.activitytest.ACTION_START")
	startActivity(intent)
}

上述代码中使用了Intent的另一个构造函数,将action的字符串传入,至于为何只有一个参数,那是因为category设置为了默认DEFAULT,调用startActivity()方法时会进行自动填充
此时便完成了一次隐式Intent的编写

每个Intent只能指定一个action,但能指定多个category,目前的Intent中只有一个默认的category,此时可通过以下代码增加,修改FirstActivity.kt中的botton1

button1.setOnClickListener {
	val intent=Intent("com.example.activitytest.ACTION_START")
	intent.addCategory("com.example.activitytest.MY_CATEGORY")
	startActivity(intent)
}

同时修改AndroidManifest.xml中的对应< category >

<category android:name="android.intent.category.MY_CATEGORY"/>

若是不对AndroidManifest.xml修改(缺少了对应的< category >)会导致程序崩溃(闪退)

隐式Intent的更多用法-启动其他程序的Activity

使用隐式Intent,不仅可以启动自己程序的Activity,还可以启动其他程序的Activity,比如在应用程序中如果需要展示一个网页,可以调用系统的浏览器的Activity来打开这个网页,修改Button1的代码如下

button1.setOnClickListener {
	val intent=Intent(Intent.ACTION_VIEW)
	intent.data= Uri.parse("https://www.baidu.com")
	startActivity(intent)
}

首先指定了Intent的action是Intent.ACTION_VIEW,这个参数是Android内置的动作,常量值为android.intent.action.VIEW,然后通过Uri.parse()将网址字符串解析为Uri对象,再调用setData()方法将这个Uri对象传递过去(利用了上文加粗字体提到的Kotlin语法糖使得这里的操作像是在对data进行赋值)

此处提到的setData()方法在上文未提及,这个方法接收一个Uri对象,用于指定当前Intent正在操作的数据,与此对应,可以在< intent-filter >中再配置一个< data >标签用于更为精确的指定当前Activity能够响应的数据,< data >可配置的主要内容如下

参数作用
android:scheme用于指定数据的协议部分,如上例中的http
android:host用于指定数据的主机名部分,如www.baidu.com
android:port用于指定数据的端口部分,一般紧跟主机名后
android:path用于指定主机名和端口之后的部分,如一段网址跟在域名之后的内容
android:mimeType用于指定可以处理的数据类型,允许一段网址中跟在域名之后的内容

只有当< data >标签中指定的内容和Intent携带的Data完全一致时,当前Activity才能响应这个data

为了理解上述内容,新建第三个Activity,命名为ThirdActivity,在third_layout.xml中创建Button3,而后在AndroidManifest.xml中修改ThirdActivity的注册信息如下

<intent-filter tools:ignore="AppLinkUrlError">
	<action android:name="android.intent.action.VIEW"/>
	<category android:name="android.intent.category.DEFAULT"/>
	<data android:scheme="https"/>
</intent-filter>

注:在输入书上代码后提示无法找到tool:ignore,而使用代码补全功能则不报错,检查后发现在导包的代码上方多了一行xmlns:tools="http://schemas.android.com/tools",增加后不再报错,但是仍然没有出现可选的效果,点击Button1仍然直接弹出浏览器

除了https协议外,还能指定很多其他的协议,比如geo显示地理位置,tel拨打电话,拨打电话的代码如下

val intent=Intent(Intent.ACTION_DIAL)
intent.data=Uri.parse("tel:10086")	//拨打10086

向下一个Activity传递数据

在Activity之间传递数据,即将数据暂存在Intent中,在切换Activity后重新取出即可,而Intent则为此提供了一系列putExtra()方法的重载,比如为了将字符串从FirstActivity传递到SecondActivity中,可以使用如下代码

button1.setOnClickListener {
	val data="Hello Java"
	val intent=Intent(this,SecondActivity::class.java)
	intent.putExtra("extra_data",data)
	startActivity(intent)
}

这里使用显式启动SecondActivity,并使用putExtra()方法传递了一个字符串,putExtra()方法接受两个参数,第一个参数是键,用于之后获取值,第二个参数是要传递的数据

然后在SecondActivity中将要传递的数据取出,代码如下

val extraData=intent.getStringExtra("extra_data")
Log.d("SecondActivity","extraData is $extraData")

上述代码实际上是调用父类的getIntent()方法,用于启动SecondActivity的Intent,然后调用getStringExtra()方法获取键值,这样就可以获取传递的数据了,如果传递的是其他类型的数据,则使用对应的getXXXExtra()方法即可

向上一个Activity传递数据

既然能向下传递数据,那么也应该可以向上传递数据,但是,由于返回上一个Activity只需要按一下Back就返回了,因此没有Intent用于传递,解决方法来自于Activity类中的startActivityForResult()方法,这个方法期望在Activity销毁的时候能返回一个结果到上一个Activity,这个方法接受两个参数,第一个参数为Intent,第二个参数是请求码,用来判断数据的来源,实现代码如下

首先修改Button1的代码

button1.setOnClickListener {
	val intent=Intent(this,SecondActivity::class.java)
	startActivityForResult(intent,1)
}

请求码为唯一值即可,接下来修改Button2的代码

button2.setOnClickListener {
	val intent=Intent()
	intent.putExtra("data_return","hello java")
	setResult(RESULT_OK,intent)
	finish()
}

上述代码构建了一个Intent,不过这个Intent仅仅用于传输数据,通过setResult()方法进行向上传输数据,该方法接收两个参数,第一个参数用于向上返回结果,一般只使用RESULT_OK和RESULT_CANCELED两个值,第二个参数则是传回带有数据的Intent,最后使用finish()销毁Activity

由于是使用startActivityForResult()方法启动的SecondActivity的,在其被销毁后会调回上一个Activity的onActivityResult()方法,因此需要重写该方法获取数据,代码如下

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
	super.onActivityResult(requestCode, resultCode, data)
		when(requestCode){
			1->if(resultCode==RESULT_OK){
			val returnedData =data?.getStringExtra("data_return")
			Log.d("FirstActivity","returned data is $returnedData")
		}
	}
}

第一个参数requestCode为启动Activity的时候的请求码,第二个参数是在返回数据时的处理结果,第三个参数即携带数据的Intent。由于在一个Activity中会有可能调用startActivityForResult()去启动很多不同的Activity,每一个数据的返回都会回调到onActivityResult中,因此需要检查requestCode的值来判断来源以及通过resultCode判断数据传输是否成功,最后获取数据

除此之外,用户可能还会通过back键来结束activity,因此,在SecondActivity中重写onBackPressed()来规避也是有必要的,代码如下

override fun onBackPressed() {
	val intent= Intent()
	intent.putExtra("data_return","hello java with BackPressed")
	setResult(RESULT_OK,intent)
	finish()
}

这样,无论是通过哪种方式返回上一个Activity,信息都会被正常返回

防止由于Activity被回收而导致的文本遗失

由于一个Activity进入了停止状态后,系统在内存不足的情况下有可能会将该Activity回收,此时如果返回该Activity,该Activity会被正常显示,但此时调用的方法不再是onRestart()而是onCreate(),但是这种情况下临时的数据状态会遗失,假设此时Activity中存在一个有信息的文本输入框,则该输入框的内容会因为这种情况而遗失

为了避免这种问题,Activity提供了一个onSaveInstanceState()回调方法,该方法保证在Activity回收之前被调用,这个方法会提供一个Bundle类型的参数,而Bundle提供一系列的方法保存数据,比如putString(),putInt()等等,这些方法需要两个参数,第一个参数是键,用于后续取值,而第二个参数则是需要保存的内容

实际运用如下

//在前一个Activity
if(savedInstanceState!=null){
	val tempData=savedInstanceState.getString("data_key")
	Log.d("FirstActivity", tempData.toString())
}
//在后一个Activity
override fun onSaveInstanceState(outState: Bundle) {
	super.onSaveInstanceState(outState)
	val tempData="Something you just typed"
	outState.putString("data_key",tempData)
}
//效果未检验

Activity的启动模式

standard

默认启动模式,在AndroidManiFest.xml中添加如下代码进行手动指定

<activity
            android:launchMode="standard">

若是通过自身调用重复创建某Activity,则每次都会创建不同的本实例,创建次数和退出时返回次数相等

singleTop

当该Activity处于返回栈顶时,进行自身调用创建Activity都会直接使用该Activity实例,返回次数仅为1,而并非处于返回栈顶时则会重复创建新的实例,效果与standard相同

singleTask

无论该Activity是否处于返回栈顶,进行自身调用创建Activity都会直接使用该Activity实例

singleInstance

是Activity的启动模式中最复杂的一个,现在有FirstAcitvity/SecondActivity/ThirdActivity,其中SecondActivity的启动模式为singleInstance,此时依次进行启动,会发现SecondActivity的taskId和另外两个不同,此时从ThirdActivity按back依次返回,返回的顺序是FirstAcitvity/ThirdActivity/SecondActivity,这是因为SecondActivity被放入另外一个返回栈中保存,此时程序从ThirdActivity开始依次返回,紧接着弹出的是FirstActivity,在该栈清空后才会弹出处于另一个栈的SecondActivity

Activity最佳实践

知晓此时处于哪一个Activity

1、创建一个BaseActivity并令其继承AppCompatActivity,且重写onCreate()方法如下

class BaseActivity:AppCompatActivity() {
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		Log.d("BaseActivity",javaClass.simpleName)
	}
}

这个类不需要在AndroidManifest.xml中注册
2、将BaseActivity用open修饰,并将所有Activity的继承改为继承至BaseActivity
3、增加过滤器BaseActivity
此时每次进入一个Activity中的界面,该Activity的类名就会被打印出来

随时随地退出程序

为了能够一键退出程序,只需要用一个专门的集合对所有Activity进行管理即可
1、新建单例类ActivityCollector,修改代码如下

object ActivityCollector {
    private val activitys= ArrayList<Activity>()
    fun addActivity(activity: Activity){
        activitys.add(activity)
    }
    fun removeActivity(activity: Activity){
        activitys.remove(activity)
    }
    fun finishAll(){
        for(activity in activitys){
            if(!activity.isFinishing){
                activity.finish()
            }
        }
        activitys.clear()
    }
}

2、修改BaseActivity中的代码

open class BaseActivity:AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("BaseActivity",javaClass.simpleName)
        ActivityCollector.addActivity(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        ActivityCollector.removeActivity(this)
    }
}

3、在需要进行一键退出的组件聆听器中调用ActivityCollector.finishAll()即可
4、若是为了保证程序完全退出,可以在finishAll()的最后加上android.os.Process.killProcess(android.os.Process.myPid()),该方法只能用于杀掉当前程序的进程,并不能杀掉其他程序的进程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值