1.实验内容
1.1 实验要求
1.Activity、Service、BroadcastReceiver、ContentProvider)的基本概念和使用方法。
2.学会如何在实际开发中灵活运用这四大组件构建Android应用程序。
3.提高Android应用开发能力和实践操作能力。
1.2实验内容
(一)安卓四大组件:Activity、Service、BroadcastReceiver、ContentProvider
Activity(活动):Activity 是用户界面的展示单元,通常表示为屏幕上的一个窗口,负责与用户进行交互。每个 Activity都是一个单独的类,用于管理用户界面和处理用户输入。
Service(服务):Service 是在后台执行长时间运行操作的组件,它没有用户界面。Service可以在不影响用户界面的情况下执行诸如播放音乐、下载文件等任务。例如,一个音乐播放器可以使用 Service 在后台播放音乐。
BroadcastReceiver(广播接收器):BroadcastReceiver是用于接收系统广播消息或应用程序内部广播的组件。广播可以是来自系统(如电池低电量警告)或应用程序(如通知其他组件某个事件发生)的消息。BroadcastReceiver可以在应用程序运行时接收广播,也可以在应用程序未运行时通过 Android 系统将消息传递给应用程序。
ContentProvider(内容提供器):ContentProvider用于管理应用程序的数据,并提供对这些数据的安全访问。ContentProvider可以让一个应用程序的数据被其他应用程序共享和访问,通常用于提供应用程序数据的共享和存储。
(二)实验内容
1.Activity组件实践(注:完成3.3,实现从firstActivity跳转到secondActivity即可)
(1)创建一个简单的Activity,并在其中添加UI元素。
(2)实现Activity之间的跳转与传值。
(3)了解Activity的生命周期,并编写代码验证。
2.Service组件实践(注:完成10.3,启动和停止Service即可)
(1)创建一个Service,用于在后台执行长时间运行的任务。
(2)通过Intent启动和停止Service。
3.BroadcastReceiver组件实践(注:完成6.4,实现强制下线功能)
4.ContentProvider组件实践(注:完成8.3,P329-P333的实践)
(1)创建一个ContentProvider,用于共享数据给其他应用程序。
(2)在另一个应用程序中访问该ContentProvider,实现数据操作。
2.实验过程
2.1Activity组件实践
①创建SecondActivity
点击empty Activity点击“Finish”完成创建,Android Studio会为我们自动生成SecondActivity.kt和second_layout.xml这两个文件。
编辑second_layout.xml,将里面的代码替换成
我们还是定义了一个按钮,并在按钮上显示Button 2。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="20212313吴剑标Button 2"
/>
</LinearLayout>
②编辑activity_second.xml文件
SecondActivity中的代码已经自动生成了一部分,我们保持默认不变即可,
package com.example.a20212313wjb
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
}
}
另外不要忘记,任何一个Activity都是需要在AndroidManifest.xml中注册的。不过幸运的是,Android Studio已经帮我们自动完成了
Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可用于启动Activity、启动Service以及发送广播等场景,由于Service、广播等概念你暂时还未涉及,那么本章我们的目光无疑就锁定在了启动Activity上面。Intent大致可以分为两种:显式Intent和隐式Intent。
③编写activity_main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_first_to_second"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="20212313吴剑标 Go to Second Activity"
android:layout_centerInParent="true"/>
</RelativeLayout>
④编写Mainactivity文件
package com.example.a20212313wjb
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button: Button = findViewById(R.id.button_first_to_second)
button.setOnClickListener {
// 创建一个Intent来启动SecondActivity
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
}
}
}
⑤实现成果
2.2Service组件实践
①首先看一下如何在项目中定义一个Service。
新建一个ServiceTest项目,然后右击com.example.servicetest→New→Service→Service,会弹出创建Service的窗口可以看到,这里我们将类名定义成MyService,Exported属性表示是否将这个Service暴露给外部其他程序访问,Enabled属性表示是否启用这个Service。将两个属性都勾中,点击“Finish”完成创建。
②修改MyService中的代码
package com.example.a20212313wjb
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log
class MyService : Service() {
private val mBinder = DownloadBinder()
class DownloadBinder : Binder() {
fun startDownload() {
Log.d("MyService", "startDownload executed")
}
fun getProgress(): Int {
Log.d("MyService", "getProgress executed")
return 0
}
}
override fun onBind(intent: Intent): IBinder {
return mBinder
}
override fun onCreate() {
super.onCreate()
Log.d("MyService", "onCreate executed")
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.d("MyService", "onStartCommand executed")
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
Log.d("MyService", "onDestroy executed")
super.onDestroy()
}
}
可以看到,这里我们又重写了onCreate()、onStartCommand()和onDestroy()这3个方法,它们是每个Service中最常用到的3个方法了。其中onCreate()方法会在Service创建的时候调用,onStartCommand()方法会在每次Service启动的时候调用,onDestroy()方法会在Service销毁的时候调用。通常情况下,如果我们希望Service一旦启动就立刻去执行某个动作,就可以将逻辑写在onStartCommand()方法里。而当Service销毁时,我们又应该在onDestroy()方法中回收那些不再使用的资源。
③编写activity_main.xml文件
添加按钮以实现启动关闭绑定解绑service
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/startServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="20212313吴剑标StartService" />
<Button
android:id="@+id/stopServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="20212313吴剑标StopService" />
<Button
android:id="@+id/bindServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="20212313吴剑标Bind Service" />
<Button
android:id="@+id/unbindServiceBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="20212313吴剑标Unbind Service" />
</LinearLayout>
④编写Mainactivity文件
package com.example.a20212313wjb
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.widget.Button
import androidx.activity.ComponentActivity
class MainActivity : ComponentActivity() {
lateinit var downloadBinder: MyService.DownloadBinder
private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
downloadBinder = service as MyService.DownloadBinder
downloadBinder.startDownload()
downloadBinder.getProgress()
}
override fun onServiceDisconnected(name: ComponentName) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val startServiceBtn = findViewById<Button>(R.id.startServiceBtn)
val stopServiceBtn = findViewById<Button>(R.id.stopServiceBtn)
startServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
startService(intent) // 启动Service
}
stopServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
stopService(intent) // 停止Service
}
val bindServiceBtn = findViewById<Button>(R.id.bindServiceBtn)
val unbindServiceBtn = findViewById<Button>(R.id.unbindServiceBtn)
bindServiceBtn.setOnClickListener {
val intent = Intent(this, MyService::class.java)
bindService(intent, connection, Context.BIND_AUTO_CREATE) // 绑定Service
}
unbindServiceBtn.setOnClickListener {
unbindService(connection) // 解绑Service
}
}
}
⑤启动和停止Service
查看service的应用,最简单的方法就是在MyService的几个方法中加入打印日志,如下所示:
2
2.3BroadcastReceiver组件实践
①先创建一个ActivityCollector类用于管理所有的Activity,代码如下所示:
package com.example.a20212313wjb
import android.app.Activity
object ActivityCollector {
private val activities = ArrayList<Activity>()
fun addActivity(activity: Activity){
activities.add(activity)
}
fun removeActivity(activity: Activity){
activities.remove(activity)
}
fun finishAll(){
for(activity in activities){
if(!activity.isFinishing){
activity.finish()
}
}
activities.clear()
}
}
②然后创建BaseActivity类作为所有Activity的父类,代码如下所示:
package com.example.a20212313wjb
import android.app.ProgressDialog.show
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
open class BaseActivity : AppCompatActivity() {
lateinit var receiver: Force0fflineReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ActivityCollector.addActivity(this)
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onResume() {
super.onResume()
val intentFilter = IntentFilter()
intentFilter.addAction("com.example.a20212313wjb.FORCE_OFFLINE")
receiver = Force0fflineReceiver()
registerReceiver(receiver, intentFilter, RECEIVER_NOT_EXPORTED)
}
override fun onPause() {
super.onPause()
unregisterReceiver(receiver)
}
override fun onDestroy() {
super.onDestroy()
ActivityCollector.removeActivity(this)
}
inner class Force0fflineReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
AlertDialog.Builder(context).apply {
setTitle("Warning")
setMessage("You are forced to be offline. Please try to login again.")
setCancelable(false)
setPositiveButton("0K") { _, _ ->
ActivityCollector.finishAll() //销毁所有Activity
val i = Intent(context, LoginActivity::class.java)
context.startActivity(i)//重新启动LoginActivity
}
show()
}
}
}
}
③首先需要创建一个LoginActivity来作为登录界面,并让Android Studio帮我们自动生成相应的布局文件。然后编辑布局文件activity_login.xml,代码如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:text="Account:"/>
<EditText
android:id="@+id/accountEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:text="password:"/>
<EditText
android:id="@+id/passwordEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:inputType="textPassword"/>
</LinearLayout>
<Button
android:id="@+id/login"
android:layout_width="200dp"
android:layout_height="60dp"
android:layout_gravity="center_horizontal"
android:text="Login"/>
</LinearLayout>
这里我们使用LinearLayout编写了一个登录布局,最外层是一个纵向的LinearLayout,里面包含了3行直接子元素。第一行是一个横向的LinearLayout,用于输入账号信息;第二行也是一个横向的LinearLayout,用于输入密码信息;第三行是一个登录按钮。
④接下来修改LoginActivity中的代码,如下所示:
package com.example.a20212313wjb
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
class LoginActivity : BaseActivity() {
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
val login : Button = findViewById(R.id.login)
login.setOnClickListener{
val accountEdit = findViewById<EditText>(R.id.accountEdit)
val account = accountEdit.text.toString()
val passwordEdit = findViewById<EditText>(R.id.passwordEdit)
val password = passwordEdit.text.toString()
//如果账号是wjb且密码是20212313,就认为登录成功
if (account == "wjb" && password == "20212313"){
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}else{
Toast.makeText(this, "不好意思,输入的账号密码错误", Toast.LENGTH_SHORT).show()
}
}
}
}
这里我们模拟了一个非常简单的登录功能。首先将LoginActivity的继承结构改成继承自BaseActivity,然后在登录按钮的点击事件里对输入的账号和密码进行判断:如果账号是wjb并且密码是20212313,就认为登录成功并跳转到MainActivity,否则就提示用户账号或密码错误。
⑤修改activity_main.xml中的代码,如下所示:
添加一个按钮用来强制下线即可
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/forceOffline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Send force offline broadcast"/>
</LinearLayout>
⑥然后修改MainActivity中的代码,如下所示:
同样非常简单,不过这里有个重点,我们在按钮的点击事件里发送了一条广播,广播的值为com.example.a20212313wjb.FORCE_OFFLINE,这条广播就是用于通知程序强制用户下线的。也就是说,强制用户下线的逻辑并不是写在MainActivity里的,而是应该写在接收这条广播的BroadcastReceiver里。这样强制下线的功能就不会依附于任何界面了。
并且添加一个intent来实现activity跳转即可
package com.example.a20212313wjb
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val forceOffline : Button = findViewById(R.id.forceOffline)
forceOffline.setOnClickListener{
val intent = Intent("com.example.a20212313wjb.FORCE_OFFLINE")
sendBroadcast(intent)
}
}
}
⑦接下来我们还需要对AndroidManifest.xml文件进行修改,代码如下所示:
讲LoginActivity设置成主activity,然后再跳转到强制下线activity
> <manifest xmlns:android="http://schemas.android.com/apk/res/android"
> xmlns:tools="http://schemas.android.com/tools">
>
> <application
> android:allowBackup="true"
> android:dataExtractionRules="@xml/data_extraction_rules"
> android:fullBackupContent="@xml/backup_rules"
> android:icon="@mipmap/ic_launcher"
> android:label="@string/app_name"
> android:roundIcon="@mipmap/ic_launcher_round"
> android:supportsRtl="true"
> android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"
> tools:targetApi="31">
> <activity
> android:name=".LoginActivity"
> android:label="20212313吴剑标:This is BroadcastReceiver"
> android:exported="true" >
> <intent-filter>
> <action android:name="android.intent.action.MAIN"/>
> <category android:name="android.intent.category.LAUNCHER"/>
> </intent-filter>
>
> </activity>
>
> <activity
> android:name=".MainActivity"
> android:label="20212313吴剑标:This is BroadcastReceiver"
> android:exported="true">
> </activity>
> </application>
>
> </manifest>
⑧实验成果
2.4ContentProvider组件实践
①在虚拟机的通讯录中增加联系人
②修改Manifes.xml文件如下:
在头部添加该语句即可
<uses-permission android:name="android.permission.READ_CONTACTS" />
③修改activity_main.xml文件
添加一个listview即可
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/contactsView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
④修改Mainactivity文件
package com.example.wjb
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.ContactsContract
import android.widget.ArrayAdapter
import android.widget.ListView
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
class MainActivity : AppCompatActivity() {
private val contactsList = ArrayList<String>()
private lateinit var adapter: ArrayAdapter<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, contactsList)
val contactsView = findViewById<ListView>(R.id.contactsView)
contactsView.adapter=adapter
if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,
arrayOf(android.Manifest.permission.READ_CONTACTS), 1)
}else{
readContacts()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when(requestCode){
1 ->{
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED){
readContacts()
}else{
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
}
}
}
}
@SuppressLint("Range")
private fun readContacts(){
//查询联系人数据
contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, null, null, null)?.apply{
while(moveToNext()){
//获取联系人姓名
val displayName = getString(getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
//获取联系人手机号
val number = getString(getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER))
contactsList.add("$displayName\n$number")
}
adapter.notifyDataSetChanged()
close()
}
}
}
⑤实验成果:
3.学习中遇到的问题及解决
- 问题1:应用闪退
- 问题1解决方案:我前面一直以为是我代码出现了问题,但是后来我重新创建了一个项目就不闪退了,真的很奇妙
- 问题2:setContentView(R.layout.activity)报错
- 问题2解决方案:是没有正确的写出包名:
package com.example.a20212313wjb
4.学习感悟、思考等)
本次实验其实就是讲书本中的例子复现出来就可以,反而我觉得前面activity和service更难理解一点,特别是如何将activity和service绑定在一起的知识点让我花了比较多的时间去理解。但是后面BroadcastReceiver、ContentProvider相对简单,只要修改xml文件然后将按钮运用起来调用函数就可以了。总的来说,这次实验让我对四大组件有了更深刻的理解。