20212408 2023-2024-2 《移动平台开发与实践》第三次实验报告

20212408 2023-2024-2 《移动平台开发与实践》第三次实验报告

一、实验内容

1.1 知识回顾

Activity(活动):Activity是用户界面的呈现者,通常表示应用程序的一个界面或一个屏幕。每个Activity都是一个单独的类,通过实现各种生命周期方法来管理其状态和行为。用户在应用程序中的交互通常会导致Activity之间的切换。

Service(服务):Service是一种在后台执行长时间运行操作的组件,它没有用户界面。Service可以用于执行诸如播放音乐、下载文件等耗时操作,而不会阻塞用户界面。

BroadcastReceiver(广播接收器):BroadcastReceiver用于接收系统或其他应用程序发送的广播消息。它可以用于响应诸如电量变化、网络连接变化等系统事件,也可以用于应用程序内部的消息通信。

ContentProvider(内容提供器):ContentProvider用于管理应用程序的数据,提供给其他应用程序访问。它通常与SQLite数据库结合使用,用于提供数据的增删改查操作,并通过URI来标识数据集。

1.2 实验目的

掌握Android四大组件(Activity、Service、BroadcastReceiver、ContentProvider)的基本概念和使用方法。
学会如何在实际开发中灵活运用这四大组件构建Android应用程序。
提高Android应用开发能力和实践操作能力。

1.3 实验环境

Android Studio开发环境
Android模拟器或真实Android设备

1.4 实验内容

Activity组件实践(注:完成3.3,实现从firstActivity跳转到secondActivity即可)
(1)创建一个简单的Activity,并在其中添加UI元素。
(2)实现Activity之间的跳转与传值。
(3)了解Activity的生命周期,并编写代码验证。

Service组件实践(注:完成10.3,启动和停止Service即可)
(1)创建一个Service,用于在后台执行长时间运行的任务。
(2)通过Intent启动和停止Service。
(3)实现Service与Activity之间的通信。(可不做)

BroadcastReceiver组件实践(注:完成6.4,实现强制下线功能)

ContentProvider组件实践(注:完成8.3,P329-P333的实践)
(1)创建一个ContentProvider,用于共享数据给其他应用程序。
(2)在另一个应用程序中访问该ContentProvider,实现数据操作。

二、实验过程

2.1 Activity组件实践

首先创建一个project,将其命名为”zujian"表示进行activity组件的实践,但是这时候这个project工程中只有一个activity,这时候就需要再创建一个activity。
这时候可以直接选择新建一个kotlin文件,然后需要在Androidmaniftest.xml中进行注册。
不过也可以选择直接创建一个empty activity通过工具栏直接创建,在Androidmaniftest.xml中就会自动注册,就不需要再去自己注册了。
在这里插入图片描述
如图可以创建一个自动注册的empty activity。
然后需要根据实验要求,实现activity之间的跳转和传值。
首先需要知道要实现activity之间的跳转需要怎么样的操作,从课本上我们知道,要实现跳转需要使用到intent,
Intent 是 Android 中用于在不同组件(如 Activity、Service、BroadcastReceiver)之间传递消息的一种机制。它可以用于启动组件、传递数据、执行操作等。
根据需要知道需要的intent操作的代码如下:

FirstActivity.kt:

val intent = Intent(this, SecondActivity::class.java)
            intent.putExtra("message", "This is SecondActivity!come from FirstActivity!")
            startActivity(intent)
SecondActivity.kt:

val message = intent.getStringExtra("message")
        val tvMessage: TextView = findViewById(R.id.tv_message)

将代码添加在activity中,并运行,可以看到如下的结果:
请添加图片描述
可以看到最上方有跳转的按钮,提示此时是在第一个activity中。

请添加图片描述
从图中可以看到,此时已经跳转至第二个activity中,并且接收到了First Activity中的消息信息。

至此完成activity之间的跳转。
然后根据实验要求,编写代码了解activity的生命周期。
从网上查找得到具体操作的代码如下:

FirstActivity.kt:
private val TAG = "MainActivity"
override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG, "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d(TAG, "onRestart")
    }
SecondActivity.kt:
private val TAG = "SecondActivity"
override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG, "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d(TAG, "onRestart")
    }

将上面的代码放到对应的activity中,运行后从log cat中查看日志结果如下:
请添加图片描述
从图中可以看到MainActivity的运行和停止,以及跳转后SecondActivity的运行。

FirstActivity.kt

2.2 Service组件实践

首先需要了解什么是Service:Service是Android中实现程序后台运行的解决方案,它非常适合执行那些不需要和用户交互而且还要求长期运行的任务。Service的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,Service仍然能够保持正常运行。

根据实验要求实现service的操作,首先需要再项目中定义一个service,这里可以通过右键点击com.example.service,然后选择new,选择service,选择生成一个service即可。
在这里插入图片描述
如图操作,可以直接生成,并且此时已经在androidmaniftest.xml中完成注册。
然后根据实验需要,对service中的内容进行重写,代码如下:

package com.example.myservice

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()
        }

}

根据课本上的要求,在Service中实现对应的启动服务,和绑定服务。
然后需要对mainactivity的布局进行设计,需要编写activity_main.xml的代码,需要实现四个按钮,分别是start service,stop service,bind service,unbind 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="StartService" />

        <Button
            android:id="@+id/stopServiceBtn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="StopService" />
        <Button
                    android:id="@+id/bindServiceBtn"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Bind Service" />

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



</LinearLayout>

然后需要编写对应的,mainactivity.kt的代码,对按钮进行监听,和对应操作的设置,具体代码如下:

package com.example.myservice

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
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.myservice.ui.theme.MyServiceTheme

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启动和结束时的logcat内容。至此Service组件的操作完成。

MyService

2.3 BroadcastReceiver组件实践

根据实验要求得知,需要实现强制下线功能,这样就需要直接关闭所有的activity,然后再回到登录的界面。根据课本上的指示,首先需要创建一个activitycollector类用于管理所有的activity。
这里可以直接右键创建一个Kotlin类,并不需要注册,根据课本写入如下代码:

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的父类,输入如下代码:

open class BaseActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ActivityCollector.addActivity(this)
    }

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

}

因为要实现强制退出,所以需要现有登录的情况,于是就先创建一个登录的activity,命名为login,然后开始设计login的布局结构,activity_login.xml代码如下:

<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中的代码:

class LoginActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        login.setOnClickListener {
            val account = accountEdit.text.toString()
            val password = passwordEdit.text.toString()
            // 如果账号是admin且密码是123456,就认为登录成功
            if (account == "admin" && password == "123456") {
                val intent = Intent(this, MainActivity::class.java)
                startActivity(intent)
                finish()
            } else {
                Toast.makeText(this, "account or password is invalid",
                          Toast.LENGTH_SHORT).show()
            }
        }
    }

}

这样来模拟一个非常简单的登录操作。如果账号是admin并且密码是123456,那么久认为登录成功并成功跳转至MainActivity。
然后编写对应的mainactivity.kt的代码,mainactivity只需要实现简单的功能显示登录成功即可。在这里只需要实现强制下线的功能即可。首先对activity_main.xml的内容进行修改,添加需要的button:

<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的代码:

class MainActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        forceOffline.setOnClickListener {
            val intent = Intent("com.example.broadcastbestpractice.FORCE_OFFLINE")
            sendBroadcast(intent)
        }
    }

}

根据课本上的知识提示得知,原来这个强制下线的功能并不是直接写在mainactivity里面,而是被包含在广播之中,这样强制下线的功能就不是依附于程序的任何地方,只需要发送这样的广播,便能够实现强制下线的功能。因此需要对上面的baseactivity的内容进行相应的修改,代码如下:

open class BaseActivity : AppCompatActivity() {

    lateinit var receiver: ForceOfflineReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ActivityCollector.addActivity(this)
    }

    override fun onResume() {
        super.onResume()
        val intentFilter = IntentFilter()
        intentFilter.addAction("com.example.broadcastbestpractice.FORCE_OFFLINE")
        receiver = ForceOfflineReceiver()
        registerReceiver(receiver, intentFilter)
    }

    override fun onPause() {
        super.onPause()
        unregisterReceiver(receiver)
    }

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

    inner class ForceOfflineReceiver : 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("OK") { _, _ ->
                    ActivityCollector.finishAll() // 销毁所有Activity
                    val i = Intent(context, LoginActivity::class.java)
                    context.startActivity(i) // 重新启动LoginActivity
                }
                show()
            }
        }

    }

}

首先是使用AlertDialog.Builder构建一个对话框。注意,这里一定要调用setCancelable()方法将对话框设为不可取消,否则用户按一下Back键就可以关闭对话框继续使用程序了。然后使用setPositiveButton()方法给对话框注册确定按钮,当用户点击了“OK”按钮时,就调用ActivityCollector的finishAll()方法销毁所有Activity,并重新启动LoginActivity。
接下来我们还需要对AndroidManifest.xml文件进行修改,代码如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.broadcastbestpractice">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".LoginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <activity android:name=".MainActivity">
        </activity>
    </application>
</manifest>

broadcast

2.4 ContentProvider组件实践

本次实验的要求是能够通过一个应用程序,直接调用跳转另外一个应用程序。根据课本上的提示,本次实验需要调用通讯录的联系人,于是需要先在虚拟机中新建联系人。
请添加图片描述
先新建了两个联系人,完成准备操作。
然后根据实验操作,完成系统联系人的读取,根据课本提示,完成以下的代码:

<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>

以上是在activity中设置一个列表,这样可以根据在通讯录中读取的内容,依次放在这个列表中,这样可以应用于相同结构未知数量的信息的表现。
然后需要对MainActivity的代码进行修改。
但是!!
课本上给出的代码

adapter = ArrayAdapter(this, R.layout.simple_list_item_1, contactsList)
        val contactsView: ListView = findViewById(R.id.contactsView)
        contactsView.adapter = adapter
        if (ContextCompat.checkSelfPermission(
                this,
                android.Manifest.permission.READ_CONTACTS
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            ActivityCompat.requestPermissions(
                this, arrayOf(Manifest.permission.READ_CONTACTS), 1
            )
        } else {
            readContacts()
        }

这段代码存在问题,不能跑通,具体的解决方法最后问题解决中会说。
然后就是需要再Maniftest.xml中进行权限的设置,需要添加android.permission.READ_CONTACTS权限,这样才能顺利访问。
实验结果如图:
请添加图片描述

三、学习中遇到的问题及解决

问题一:对logcat理解不到位

开始进行实验一查看activity生命周期时,并不知道写完的日志要在下方logcat中查找,而是尝试输出生命周期,发现并不能实现,后面再认真查看课本后才知道要在logcat中查看。

问题二:在运行broadcast实验时闪退,logcat中出现未知报错。

在将课本上的代码放到Android studio中并尝试时,可以提示安装,但是内容会被闪退,但是代码上面没有提示任何错误,logcat中弹出如下报错:FATAL EXCEPTION: main
Process: com.example.logout, PID: 19000
java.lang.RuntimeException: Unable to resume activity {com.example.logout/com.example.logout.LoginActivity}: java.lang.SecurityException: com.example.logout: One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified when a receiver isn’t being registered exclusively for system broadcasts
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4962)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4995)
…等等很多很多的红色报错,但是不知道如何解决,于是选择摆烂的做法,重新新建一个project,然后就可以跑通了。

问题三:在ContentProvider实验中,课本代码出现问题,不能直接复制使用,需要自己进行调整。

在实验第四部分,课本给出的代码中,直接复制过去会报错,需要我们自己调节,总结下来就是两个问题,一个是文件路径的问题,在引用maniftest时其前面少了一个文件名,需要添加android这样才能正常运行,然后就是存在声明错误,有些参数没有进行提前声明,需要自己操作。

四、实验总结

这次实验让我对安卓开发有了更深入的了解。之前我对安卓四大组件的理解还停留在表面,认为只要了解基本原理就可以了。但是在实际操作中,我发现要想熟练运用这些组件,还需要对其参数和方法有更深入的了解。特别是在编写BroadcastReceiver组件和ContentProvider组件时,需要考虑的问题很多,实验过程中,不仅需要理解Android四大组件的基本原理,还需要掌握大量的API,并且要有耐心和细心去分析和解决各种可能出现的问题和bug。通过不断的实践和尝试,我对Android开发有了更深入的了解,也提高了自己解决问题的能力。
通过解决实验中遇到的各种问题,我逐渐掌握了安卓开发中常见的技巧和方法,也提高了自己解决问题的能力。在未来的学习和工作中,我会继续努力,提升自己的技术水平,同时,我也会继续保持学习的热情,不断探索和尝试新的技术,以应对快速变化的技术发展。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值