20212313 2023-2024-2 《移动平台开发与实践》第3次作业

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文件然后将按钮运用起来调用函数就可以了。总的来说,这次实验让我对四大组件有了更深刻的理解。

参考资料

  • 19
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值