《Android》Chap.10 探究Service

Service简介

Service是Android中实现程序后台运行的解决方案,适用于执行不需要和用户交互且要求长期运行的任务。

  • Service的运行不依赖任何用户界面
  • 当某个进程结束的时候,依赖于该进程的Service也会停止
  • Service并不会自动开启线程

Android 多线程编程

线程的基本用法

继承自Thread的类

//定义线程
class MyThread : Thread(){
	override fun run(){
		//具体操作
	}
}
//启动线程
MyThread().start()

实现Runnable接口

//定义线程
class MyThread : Runnable{
	override fun run(){
		//具体操作
	}
}
//启动线程
val myThread = MyThread()
Thread(myThread).start()

Lambda表达式

Thread{
	//具体操作
}.start()
thread{
	//具体操作
}

在子线程中更新UI

因为Android的UI是线程不安全的,所以想要更行应用程序里的UI元素,必须要在主线程中进行。
如果尝试直接在子线程中更新的话

mainBinding.changeTextBtn.setOnClickListener {
    thread {
        mainBinding.textView.text = "Nice"
    }
}

点击按钮后就会产生如下报错
在这里插入图片描述

异步消息处理机制

如果有些时候必须在子线程里执行一些耗时任务,然后根据任务的执行结果来更新相应的UI控件,就可以使用异步消息处理机制

class MainActivity : AppCompatActivity() {

    private lateinit var mainBinding: ActivityMainBinding

    val updateText = 1 //用于表示更新textView的动作
    val handler = object : Handler(Looper.getMainLooper()){
        override fun handleMessage(msg: Message) {
            when(msg.what){ 
            	//如果Message的what字段等于updateText,就更新UI
                updateText -> mainBinding.textView.text = "Nice"
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(mainBinding.root)

        mainBinding.changeTextBtn.setOnClickListener {
            thread {
            	//在子线程中创建一个what值为updateText的Message对象并发送给Handler
                val msg = Message()
                msg.what = updateText
                handler.sendMessage(msg)
            }
        }

    }
}

解析异步消息处理机制

组成部分

Android中的异步消息处理主要由4个部分组成:

  • Message
    Message是在线程之间传递的消息,它可以在内部携带少量的信息, 用于在不同线程之间传递数据
  • Handler
    Handler主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage()方法、post()方法等,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage()方法中。
  • MessageQueue
    MessageQueue主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
  • Looper
    Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个无限循环当中,然后每当发现MessageQueue中存在一条消息时,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中只会有⼀个Looper对象。

处理流程

  1. 需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。
  2. 当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。
  3. 这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息。
  4. 最后分发回Handler的handleMessage()方法中。由于Handler的构造函数中我们传入Looper.getMainLooper(),所以此时handleMessage()方法中的代码也会在主线程中运行,在这里就可以进行UI操作了。

使用AsyncTask

AsyncTask是一个抽象类,在使用时需要创建一个子类去继承他。在继承时可以为他指定3个泛型参数。

  • Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
  • Progress:在后台任务执行时,如果需要在界面上显示当前的进度, 则使用这里指定的泛型作为进度单位。
  • Result:当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。

在进行实际的操作前,还需要重写AsyncTask中的几个方法才能完成对任务的定制。

  1. onPreExecute():这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
  2. doInBackground(Params...):这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理 所有的耗时任务。任务一旦完成,就可以通过return语句将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Unit,就可以不返回任务执行结果。注意:在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用。publishProgress (Progress...)方法来完成。
  3. onProgressUpdate(Progress...):当在后台任务中调用了publishProgress(Progress...)方法后,onProgressUpdate (Progress...)方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
  4. onPostExecute(Result):当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据进行一些UI操作,比如说提醒任务执行的结果,以及关闭进度条对话框等。

较完整的自定义AsyncTask形式如下:

class DownloadTask : AsyncTask<Unit,Int,Boolean>() {

    /**
     * 在后台任务开始之前调用,用于执行一些界面上的初始化操作
     * eg:显示一个进度条对话框
     */
    override fun onPreExecute() {
    }

    /**
     * 在子线程中处理的耗时任务。完成后通过return语句将执行结果返回
     * eg:执行具体的下载任务
     */
    override fun doInBackground(vararg params: Unit?): Boolean? {
        return true
    }

    /**
     * 更新UI
     */
    override fun onProgressUpdate(vararg values: Int?) {
    }

    /**
     * 后台任务执行完毕后调用
     */
    override fun onPostExecute(result: Boolean?) {
    }
}

启动任务语句:

DownloadTask().execute()

Service的基本用法

定义

在项目目录上右击→New→Service→Service 就可以新建一个Service。

class MyService : Service() {
    
    override fun onBind(intent: Intent?): IBinder? {
        TODO("Not yet implemented")
    }
    
    /**
     * Service创建时调用
     */
    override fun onCreate() { 
        super.onCreate()
    }

    /**
     * Service每次启动时调用
     */
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return super.onStartCommand(intent, flags, startId)
    }

    /**
     * Service销毁时调用
     */
    override fun onDestroy() {
        super.onDestroy()
    }
    
}

Service需要在AndroidManifest.xml中注册才能生效。
在这里插入图片描述
其中,exported属性表示是否将这个Service暴露给外部其他程序访问;enabled属性表示是否启用这个Service。

启动和停止

通过设置点击事件和Log日志,可以观察到Service的方法调用

class MainActivity : AppCompatActivity() {

    private lateinit var mainBinding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(mainBinding.root)

        mainBinding.startBtn.setOnClickListener {
            val intent = Intent(this,MyService::class.java)
            startService(intent) //启动Service
        }

        mainBinding.stopBtn.setOnClickListener {
            val intent = Intent(this,MyService::class.java)
            stopService(intent) //停止Service
        }
    }
}

在这里插入图片描述
在第一次点击启动按钮时,同时调用onCreat()onStartCommand()方法,但再次点击时,无论几次,就只调用onStartCommand()方法。点击停止按钮时,调用onDestory()方法。

与Activity间的通信

通过Service中的onBind()方法可以实现在Avtivity中调用Service的方法。

首先在MyService中新建一个继承自BinderDownloadBinder类,创建实例后再onBind()方法中返回。

class MyService : Service() {

    private val TAG = "MyService.out"

    private val mBinder = DownloadBinder()

    class DownloadBinder : Binder(){
        fun startDownload(){
            Log.d("DownloadBinder.out","startDownload()")
        }

        fun getProgress() : Int{
            Log.d("DownloadBinder.out","getProgress()")
            return 0
        }
    }

    override fun onBind(intent: Intent?): IBinder? {
        return mBinder
    }
    
    override fun onCreate() {
        super.onCreate()
        Log.d(TAG,"onCreate()")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d(TAG,"onStartCommand()")
        return super.onStartCommand(intent, flags, startId)
    }
    
    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG,"onDestroy()")
    }

}

然后,在MainActivity中创建ServiceConnection的匿名类实现

class MainActivity : AppCompatActivity() {

    private lateinit var mainBinding: ActivityMainBinding

    lateinit var downloadBinder : MyService.DownloadBinder

    private val connection = object : ServiceConnection{
        /**
         * Activity和Service成功绑定时调用
         */
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            downloadBinder = service as MyService.DownloadBinder
            downloadBinder.startDownload()
            downloadBinder.getProgress()
        }

        /**
         * Service的创建京城崩溃或被杀掉时调用
         */
        override fun onServiceDisconnected(name: ComponentName?) {
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mainBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(mainBinding.root)

        mainBinding.bindServiceBtn.setOnClickListener {
            val intent = Intent(this,MyService::class.java)
            // BIND_AUTO_CREATE表示在Activity和Service绑定厚自动创建Service
            bindService(intent,connection,Context.BIND_AUTO_CREATE) //绑定Service
        }

        mainBinding.unbindServiceBtn.setOnClickListener {
            unbindService(connection) //解绑Service
        }
    }
}

分别点击绑定和解绑按钮
在这里插入图片描述
可以看到在MainActivity中调用了MyService中的方法。

使用前台Service

从Android 8.0系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。
前台Service一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,类似于通知的效果。
由于状态栏中一直有一个正在运行的图标,相当于应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。另外,用户也可以通过下拉状态栏清楚地知道当前什么应用正在运行,因此也不存在某些恶意应用长期在后台偷偷占用手机资源的情况。

只需要修改MyService中onCreate()方法就能实现

override fun onCreate() {
    super.onCreate()
    Log.d(TAG,"onCreate()")
    val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        val channel = NotificationChannel(
            "MyService","前台Service通知",NotificationManager.IMPORTANCE_DEFAULT)
        manager.createNotificationChannel(channel)
    }
    val intent = Intent(this,MainActivity::class.java)
    val pi = PendingIntent.getActivity(this,0,intent,0)
    val notification = NotificationCompat.Builder(this,"my_service")
        .setContentTitle("This is content title")
        .setContentText("This is content text")
        .setSmallIcon(R.drawable.ic_small)
        .setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.ic_small))
        .setContentIntent(pi)
        .build()
    startForeground(1,notification)
}

还需要在AndroidManifest.xml文件中进行权限声明
在这里插入图片描述
在运行是点击开启Service按钮后下拉通知栏就能看到提示了。
在这里插入图片描述

使用IntentService

一个标准的Service在使用时,需要在每个具体的方法里开启一个子线程用于处理一些耗时的逻辑,并且在任务执行完成后要注意调用stopService()
而使用IntentService类则没有这样的问题。
新建一个MyIntentService

class MyIntentService : IntentService("MyIntentService") {

    private val TAG = "MyIntentService.out"

	/**
	* 处理耗时操作 运行在子线程中
	*/
    override fun onHandleIntent(intent: Intent?) {
        TODO("Not yet implemented")
        Log.d(TAG,"Thread id is ${Thread.currentThread().name}")
    }

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

写好后在MainActivity中添加一个开启IntentService的点击事件

mainBinding.startIntentServiceBtn.setOnClickListener {
   Log.d("MainActivity.out","Thread id is ${Thread.currentThread().name}")
   val intent = Intent(this,MyIntentService::class.java)
   startService(intent)
}

点击按钮后查看日志它会在执行完任务后自动结束。
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
java.lang.ClassNotFoundException是Java中的一个异常类,表示在运行时找不到指定的类。当Java虚拟机(JVM)尝试加载一个类时,如果找不到该类的定义,就会抛出ClassNotFoundException异常。 在你的引用中,你提到了一个java.lang.ClassNotFoundException: com.example.YourServlet异常。这个异常通常发生在使用Java Servlet时,当服务器无法找到指定的Servlet类时抛出。 要解决这个异常,你可以采取以下步骤: 1. 确保你的类路径正确:检查你的项目配置和部署环境,确保Servlet类的路径正确。如果你使用的是Java Web容器(如Tomcat),请确保Servlet类位于正确的目录下。 2. 检查类名拼写:检查你的代码中的类名拼写是否正确。确保类名的大小写和包名的正确性。 3. 检查依赖项:如果你的Servlet类依赖于其他类或库,确保这些依赖项已正确地添加到你的项目中,并且可以在运行时访问到。 4. 清理和重新构建项目:有时,编译错误或构建问题可能导致类文件无法正确生成。尝试清理和重新构建你的项目,以确保所有的类文件都已正确生成。 5. 检查类加载器:如果你在自定义类加载器中加载类,确保你的类加载器能够正确找到并加载指定的类。 6. 检查运行时环境:如果你在不同的环境中运行你的应用程序(例如开发环境和生产环境),请确保运行时环境中存在所需的类。 下面是一个示例代码,演示了如何处理java.lang.ClassNotFoundException异常: ```java try { Class.forName("com.example.YourServlet"); } catch (ClassNotFoundException e) { e.printStackTrace(); // 处理异常的代码 } ``` 这段代码尝试加载名为"com.example.YourServlet"的类。如果找不到该类,就会抛出ClassNotFoundException异常,并且可以在catch块中处理该异常。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值