当前位置: 首页 > news >正文

Android Service学习笔记

1、Service介绍

Android Service(服务)是 Android 四大组件之一,主要作用是执行后台操作。它是一个后台运行的组件,执行长时间运行且不需要用户交互的任务。即使应用被销毁也依然可以工作。
Service并不是运行在一个独立的进程当中的,而是依赖于创建Service时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于该进程的Service也会停止运行。
服务基本上包含两种状态:

  • Started:当 Android 的应用程序组件,如活动,通过 startService() 启动了服务,则服务是 Started 状态。一旦启动,服务可以在后台无限期运行,即使启动它的组件已经被销毁。
  • Bound:当 Android 的应用程序组件通过 bindService() 绑定了服务,则服务是 Bound 状态。Bound 状态的服务提供了一个客户服务器接口来允许组件与服务进行交互,如发送请求,获取结果,甚至通过 IPC 来进行跨进程通信。

服务拥有生命周期方法,可以实现监控服务状态的变化,可以在合适的阶段执行工作。例如,onStartCommand() 方法会在其他组件(如活动)通过调用 startService() 来请求启动服务时被系统调用。

要创建服务,你需要创建一个继承自 Service 基类或者它的已知子类的 Java 类。例如,下面是一个简单的 Service 类的示例:

public class MyService extends Service {@Overridepublic void onCreate() {// 服务被创建时调用}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {// 其他组件通过调用 startService() 来请求启动服务时,系统调用该方法return super.onStartCommand(intent, flags, startId);}@Nullable@Overridepublic IBinder onBind(Intent intent) {// 当其他组件想要通过 bindService() 来绑定服务时,系统调用该方法return null;}@Overridepublic boolean onUnbind(Intent intent) {// 当客户中断所有服务发布的特殊接口时,系统调用该方法return super.onUnbind(intent);}@Overridepublic void onDestroy() {// 当服务不再有用或者被销毁时,系统调用该方法}
}

与之对应的Kotlin代码如下:

class MyService : Service() {// 当其他组件通过bindService来绑定服务时被调用override fun onBind(intent: Intent): IBinder {// 返回一个IBinder对象TODO()}// 在Service被创建时调用override fun onCreate() {super.onCreate()}// 当其他组件通过startService来请求启动Service时,该方法被调用override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {return super.onStartCommand(intent, flags, startId)}// 当服务不再有用或者被销毁时,系统调用该方法override fun onDestroy() {super.onDestroy()}// 当客户中断所有服务发布的特殊接口时,系统调用该方法override fun onUnbind(intent: Intent?): Boolean {return super.onUnbind(intent)}
}

onCreate()方法是在Service第一次创建的时候调用的,而onStartCommand()方法则在每次启动Service的时候都会调用。

2、异步消息处理机制

Android的异步消息处理主要由以下四个部分组成:Message、Handler、MessageQueue和Looper。

  1. Message:Message是在线程之间传递的消息,它可以携带少量的信息,用于在不同线程之间传递数据。我们可以使用Message的whatarg1arg2字段来携带整型数据,或者使用obj字段来携带一个Object对象。

  2. Handler:Handler主要用于发送和处理消息。我们可以使用Handler的sendMessage()post()等方法来发送消息,发送出的消息会最终传递到Handler的handleMessage()方法中进行处理。

  3. MessageQueue:MessageQueue是消息队列,主要用于存放所有通过Handler发送的消息。这些消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。

  4. Looper:Looper是每个线程中的MessageQueue的管家,当调用Looper的loop()方法后,就会进入一个无限循环中。每当发现MessageQueue中存在一条消息时,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中只会有一个Looper对象。

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

class MainActivity : AppCompatActivity() {private val updateText = 1 // 用于标识哪个动作private lateinit var textView: TextView// Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般//是使用Handler的sendMessage()方法、post()方法等,而发出的消息经过一系列地辗//转处理后,最终会传递到Handler的handleMessage()方法中private val handler = object : Handler(Looper.getMainLooper()){override fun handleMessage(msg: Message) {// 在主线程中进行UI操作when(msg.what){updateText -> textView.text = msg.obj.toString()}}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)textView = findViewById<TextView>(R.id.textView)val changeTextBtn = findViewById<Button>(R.id.changeTextBtn)changeTextBtn.setOnClickListener{// 开启一个新的线程thread {// Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间传递数据val message = Message()message.what = updateTextmessage.arg1 = 1 // 携带整型数据message.arg2 = 2message.obj = "你好啊" // 携带对象类型数据// 发送Message对象handler.sendMessage(message)// 通过post方法发送一个 Runnable 对象,这个 Runnable 对象会被添加到消息队列的尾部handler.post(Runnable { Log.d("Handler", "post") })}}}
}
3、Service的使用

例如一个后台下载服务的简单实现

class MyService : Service() {private val mBinder = DownloadBinder()// 当其他组件通过bindService来绑定服务时被调用override fun onBind(intent: Intent): IBinder {// 返回一个IBinder对象return mBinder}// 在Service被创建时调用override fun onCreate() {super.onCreate()}// 当其他组件通过startService来请求启动Service时,该方法被调用override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {return super.onStartCommand(intent, flags, startId)}// 当服务不再有用或者被销毁时,系统调用该方法override fun onDestroy() {super.onDestroy()}// 当客户中断所有服务发布的特殊接口时,系统调用该方法override fun onUnbind(intent: Intent?): Boolean {return super.onUnbind(intent)}class DownloadBinder : Binder() {fun startDownload() {Log.d("MyService", "startDownload executed")}fun getProgress(): Int {Log.d("MyService", "getProgress executed")return 0}}
}
class MainActivity2: AppCompatActivity(){private lateinit var mBinder: MyService.DownloadBinder// ServiceConnection用于监听服务的状态, 以便于在服务绑定成功时执行相应的逻辑private val connection = object : ServiceConnection {override fun onServiceConnected(name: ComponentName?, service: IBinder?) {mBinder = service as MyService.DownloadBindermBinder.startDownload()mBinder.getProgress()}override fun onServiceDisconnected(name: ComponentName?) {}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// 启动服务findViewById<Button>(R.id.startServiceBtn).setOnClickListener {val intent = Intent(this, MyService::class.java)startService(intent)}// 停止服务findViewById<Button>(R.id.stopServiceBtn).setOnClickListener {val intent = Intent(this, MyService::class.java)stopService(intent)}// 绑定服务,这样Activity和Service就建立了关联findViewById<Button>(R.id.bindServiceBtn).setOnClickListener {val intent = Intent(this, MyService::class.java)// 传入参数:intent, ServiceConnection, flags,// flags一般传入BIND_AUTO_CREATE表示在Activity和Service建立关联后自动创建ServicebindService(intent, connection, BIND_AUTO_CREATE)}// 解绑服务findViewById<Button>(R.id.unbindServiceBtn).setOnClickListener {unbindService(connection)}}
}
4、前台Service

从Android 8.0系统开始,只有当应用保持在前台可见状态的情况下,Service
才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。而如果你希望Service能够一直保持运行状态,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。

class FrontService : Service(){override fun onCreate() {super.onCreate()val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManagerval channel = NotificationChannel("my_service","前台Service通知",NotificationManager.IMPORTANCE_DEFAULT)manager.createNotificationChannel(channel)val intent = Intent(this, MainActivity::class.java)val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)val notification = NotificationCompat.Builder(this, "my_service").setContentTitle("Content title").setContentText("content text").setSmallIcon(R.mipmap.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher_round)).setContentIntent(pendingIntent).build()// 第一个参数是通知的id,类似于notify()方法的第一个参数;第二个参数则是//构建的Notification对象。调用startForeground()方法后就会让MyService变成一个前//台Service,并在系统状态栏显示出来。startForeground(1, notification)}override fun onBind(intent: Intent?): IBinder? {TODO("Not yet implemented")}}

这里需要声明权限,同时指定Service的foregroundServiceType(根据实际选择)

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<service android:name=".FrontService"android:enabled="true"android:exported="true"android:foregroundServiceType="mediaPlayback"/>
4、IntentService

Service中的代码都是默认运行在主线程当中的,如果直接在Service里处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。
应该在其他线程处理耗时操作,如下所示。

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {thread {// 开启一个线程执行耗时操作stopSelf() // 操作执行完毕关闭Service}return super.onStartCommand(intent, flags, startId)}

Android提供了IntentService,这种Service在执行完后自动销毁。

class MyIntentService: IntentService("MyIntentService"){// 新增了一个方法,该方法已经在子线程中执行,所以将耗时操作放在这里override fun onHandleIntent(intent: Intent?) {TODO("Not yet implemented")}// 其他方法和普通Service一样override fun onCreate() {super.onCreate()}
}

相关文章:

Android Service学习笔记

1、Service介绍 Android Service&#xff08;服务&#xff09;是 Android 四大组件之一&#xff0c;主要作用是执行后台操作。它是一个后台运行的组件&#xff0c;执行长时间运行且不需要用户交互的任务。即使应用被销毁也依然可以工作。 Service并不是运行在一个独立的进程当…...

amr文件怎么转换成mp3?超好用的四种转换方法介绍!

amr文件怎么转换成mp3&#xff1f;在当今数字化时代&#xff0c;音频格式的多样性给我们带来了更广泛的选择&#xff0c;其中AMR格式就是其中之一&#xff0c;AMR格式在录音和通话领域得到广泛应用&#xff0c;但与此同时&#xff0c;它也存在一些挑战和局限性&#xff0c;尽管…...

翻转数位00

题目链接 翻转数位 题目描述 注意点 可以将一个数位从0变为1找出能够获得的最长的一串1的长度&#xff08;必须是连续的&#xff09; 解答思路 参照题解使用动态规划解决本题&#xff0c;对于任意一个位置i&#xff0c;dp[i][0]表示到达且包含第i位不翻转0最长1的长度&…...

工具:安装R语言的R包的各种方法

欢迎大家关注全网生信学习者系列&#xff1a; WX公zhong号&#xff1a;生信学习者Xiao hong书&#xff1a;生信学习者知hu&#xff1a;生信学习者CDSN&#xff1a;生信学习者2 介绍 R语言提供的大量R包为众多研究者提供了足够的工具&#xff0c;但是如何安装R包是很多人在使…...

注意力机制和Transformer模型各部分功能解释

文章目录 Transformer1、各部分功能解释2、通过例子解释a.输入预处理位置编码b.Encoder 的处理c.Decoder的输入Decoder的工作流程d.输出预测总结 Attention代码和原理理解 Transformer 运行机理&#xff1a; &#xff08;1&#xff09;假设我们需要进行文本生成任务。我们将已…...

短路是怎么形成的

1. 短路分为电源短路和用电器短路。 电源短路&#xff1a;电流不经过任何用电器&#xff0c;直接由正极经过导线流向负极&#xff0c;由于电源内阻很小&#xff0c;导致短路电流很大&#xff0c;特别容易烧坏电源。 用电器短路&#xff1a;也叫部分电路短路&#xff0c;即一根…...

【ZZULIOJ】1106: 回文数(函数专题)

题目描述 一个正整数&#xff0c;如果从左向 右读&#xff08;称之为正序数&#xff09;和从右向左读&#xff08;称之为倒序数&#xff09;是一样的&#xff0c;这样的数就叫回文数。输入两个整数m和n&#xff08;m<n)&#xff0c;输出区间[m&#xff0c;n]之间的回文数。…...

数据库设计规范总结

数据库设计规范总结 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 数据库设计规范是指在设计数据库时应该遵循的一系列规则和标准&#xff0c;旨在提高数据库…...

深度学习(九)——神经网络:最大池化的作用

一、 torch.nn中Pool layers的介绍 官网链接&#xff1a; https://pytorch.org/docs/stable/nn.html#pooling-layers 1. nn.MaxPool2d介绍 nn.MaxPool2d是在进行图像处理时&#xff0c;Pool layers最常用的函数 官方文档&#xff1a;MaxPool2d — PyTorch 2.0 documentation &…...

「前端+鸿蒙」鸿蒙应用开发-ArkTS语法说明-组件声明

ArkTS 是鸿蒙应用开发中的一个框架,它允许开发者使用 TypeScript 语法来创建声明式的用户界面。在 ArkTS 中,组件声明是构建 UI 的基础。以下是 ArkTS 快速入门的指南,包括组件声明的语法说明和示例代码。 ArkTS 快速入门 - 语法说明 - 组件声明 组件基础 在 ArkTS 中,组…...

python的subprocess 模块

subprocess 模块是 2.4 版本中新增的模块, 它允许您生成新进程&#xff0c;连接到它们的 输入 / 输出 / 错误 管道&#xff0c;并获得它们的返回码 (状态信息), 该模块的目的在于取代几个较旧的模块和功能 subprocess 模块可以用于执行系统命令, 拿到执行的结果, 速度比较的快…...

【Arc gis】使用DEM提取流域范围

地址&#xff1a;arcgis DEM 提取流域范围&#xff08;详细教程&#xff09;(空间分析--Hydrology)_gis的gridcode是什么意思-CSDN博客...

大模型技术工程师:抓住时代机遇,成为行业精英_

伴随AI大模型的火热&#xff0c;中国科技大厂们正在掀起一场「跑步AI化」的风暴。从顶层战略到业务线重构&#xff0c;AI无疑已成为大厂们押注未来的新故事。 大模型时代已经到来 大模型已成为全球竞争热点&#xff0c;一个大模型时代已经到来。 大模型具备三个特点&#xf…...

孟德尔随机化R包:TwoSampleMR和MR-PRESSO安装

1. 孟德尔随机化R包 看一篇文章&#xff0c;介绍孟德尔随机化分析&#xff0c;里面推荐了这两个R包&#xff0c;安装了解一下&#xff1a; Methods:Genome-wide association study (GWAS) data for autoimmune diseases and AMD were obtained from the IEU Open GWAS databas…...

6月18日 Qtday4

作业day4.1 作业4.2...

Vue3模拟国足18强赛抽签

Vue3国足18强赛抽签 国足遇到这个对阵&#xff0c;能顺利出现吗&#xff1f; 1、系统演示 Vue3模拟国足18强赛抽签 2、关键代码 开始抽签 <script setup> import FenDang from "/components/chouqian/FenDang.vue"; import {ref} from "vue";le…...

mesa编译器nir信息储存问题

概述 本来想将一个完整的可以从hlsl-dxil-spirv-nir-code的项目划分为两个动态库a.dll与b.dll。应用程序调用a.dll与b.dll执行相同的过程。 a.dll&#xff1a;执行dxil-spirv-nir前端相关的转换。 b.dll&#xff1a;执行nir-code的转换。 应用程序调用dxc实现hlsl-dxil的过程&…...

windows下mysql设置开机自启动

windows下mysql设置开机自启动 情况1.mysql服务不存在情况2.mysql服务已存在 我们先检查一下电脑是否存在mysql服务 此电脑(右键)—>管理—>服务 看一下能不能找到相关mysql 服务 情况1.mysql服务不存在 以管理员的身份运行命令窗口,找到mysqld.exe 所在的路径 命令如下…...

L2-002 链表去重(C++)

给定一个带整数键值的链表 L&#xff0c;你需要把其中绝对值重复的键值结点删掉。即对每个键值 K&#xff0c;只有第一个绝对值等于 K 的结点被保留。同时&#xff0c;所有被删除的结点须被保存在另一个链表上。例如给定 L 为 21→-15→-15→-7→15&#xff0c;你需要输出去重后…...

异或运算在面试题中的应用

异或运算 是 涉及到数据位运算时常见的处理方式。如何进行异或运算?在对应位上,相同为0,不同1,但其实两个数据异或运算就是进行无进位加法。 例如: int a = 7, b = 6, a ^b = ? 算法1: 相同为0,不同为1 a ^ b= : 0 0 0 1 算法2: 无进位…...

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 单词大师(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…...

LabVIEW在SpaceX的应用

结合真实的资料介绍LabVIEW在SpaceX的应用&#xff0c;涵盖自动化测试系统、数据采集与监控、可视化与分析、模块化设计与扩展&#xff0c;以及效率与可靠性的提高。 ​ 自动化测试系统 LabVIEW在SpaceX的自动化测试系统中发挥了关键作用。自动化测试是确保SpaceX火箭及其子系…...

【Android面试八股文】讲一讲String、StringBuffer和StringBuilder在进行字符串操作时候的效率

文章目录 一、String二、StringBuffer三、StringBuilder四、String、StringBuffer和StringBuilder的效率测试五、String、StringBuffer和StringBuilder的选择一、String String是不可变的,final修饰,任何对String的操作都会创建一个新的String对象。在进行大量字符串拼接或修…...

[自动驾驶 SoC]-4 特斯拉FSD

FSD, 参考资料来源FSD Chip - Tesla - WikiChip 另外可参考笔者之前分享文章&#xff1a;[自动驾驶技术]-6 Tesla自动驾驶方案之硬件&#xff08;AI Day 2021&#xff09;&#xff0c;​​​​​​​[自动驾驶技术]-8 Tesla自动驾驶方案之硬件&#xff08;AI Day 2022&#xf…...

PostgreSQL源码分析——物化视图

我们前面分析完视图后&#xff0c;这里再继续分析一下物化视图&#xff0c;其实现原理是不相同的&#xff0c;需要注意&#xff0c;物化视图等于是将返回的结果集缓存起来&#xff0c;而视图是查询重写&#xff0c;结果需要重新进行计算。 create materialized view matvt1 as…...

操作系统入门系列-MIT6.828(操作系统工程)学习笔记(七)---- 系统调用函数与GDB(Lab: system calls)

系列文章目录 操作系统入门系列-MIT6.828&#xff08;操作系统工程&#xff09;学习笔记&#xff08;一&#xff09;---- 操作系统介绍与接口示例 操作系统入门系列-MIT6.828&#xff08;操作系统工程&#xff09;学习笔记&#xff08;二&#xff09;---- 课程实验环境搭建&am…...

ORA-12560: TNS:协议适配器错误

项目场景&#xff1a; 由于最近一直没有连接oracle&#xff0c;然后之前windows也是正常可以启动oracle&#xff0c;正常连接。无论是SQL Developer还是SQL PLUS命令&#xff0c;都能正常连接和操作。 问题描述 这两天刚好用SQL Developer工具连接&#xff0c;然后报错&#…...

不容小觑的“白纸黑字”:银行重空凭证的风险与防控

一、定义与重要性 定义&#xff1a; 银行重空凭证&#xff0c;也称为重要空白凭证&#xff0c;是银行专业术语&#xff0c;指银行印制的无面额、经银行或单位填写金额并签章后&#xff0c;即具有支取款项效力的空白凭证。 重要性&#xff1a; 它是银行资金支付的重要工具&a…...

30v-180V降3.3V100mA恒压WT5107

30v-180V降3.3V100mA恒压WT5107 WT5107是一款恒压单片机供电芯片&#xff0c;它可以30V-180V直流电转换成稳定的3.3V直流电&#xff08;最大输出电流300mA&#xff09;&#xff0c;为各种单片机供电。WT5107的应用也非常广泛。它可以用于智能家居、LED照明、电子玩具等领域。比…...

Spring Boot 和 Spring Cloud 的区别及选型

Spring Boot 和 Spring Cloud 是现代 Java 开发中非常流行的两个框架&#xff0c;它们分别解决了不同层次的问题。本文将详细介绍 Spring Boot 和 Spring Cloud 的区别&#xff0c;以及在不同场景下如何选择合适的技术。 Spring Boot 什么是 Spring Boot Spring Boot 是一个…...

顺德手机网站设计价位/菏泽百度推广公司电话

版本控制git之二 分支 有人把 Git 的分支模型称为它的‘必杀技特性’&#xff0c;也正因为这一特性&#xff0c;使得 Git 从众多版本控制系统中脱颖而出。 为何 Git 的分支模型如此出众呢&#xff1f; Git 处理分支的方式可谓是难以置信的轻量&#xff0c;创建新分支这一操作几…...

福田网站建设电话/搜索排行

谁都知道清晰度、流畅度、延时是直播的几大关键指标&#xff0c;尤其是在互动直播、移动直播大行其道的当下&#xff0c;延时更是成为了各直播平台、CDN、直播云服务商比拼的至高点。然而&#xff0c;但是&#xff0c;BUT&#xff0c;你真的了解延时么&#xff1f;不能全面了解…...

wordpress好难/如何做好网络营销推广

一个好的设计师会得到企业争相抢夺的&#xff0c;工作需求每天都有&#xff0c;但总有一些冥冥之中会被更多人关注&#xff0c;如实事热点、周年庆典、大版本发布、热门合作等&#xff0c;那这些被更多关注的项目是否就是传说中的大型项目呢?视觉设计师在这样的项目中&#xf…...

济南做设计公司网站/百度关键词快排

KMP算法用于串的模式匹配&#xff0c;主串S&#xff0c;子串T&#xff08;也叫模式串&#xff09;&#xff0c;模式匹配意思是从S中找出跟T一样的子串&#xff0c;就是说判断S是否包含T&#xff0c;时间复杂度O&#xff08;mn&#xff09;&#xff0c;实现这个算法关键有两步&a…...

网站注销/网络营销的重要性与意义

前言 kafka提供了消费组命令工具管理消费组&#xff1a;kafka-consumer-groups.sh&#xff0c;在0.11版本之后引入位移重置功能&#xff0c;重置策略如下&#xff08;引用自官方文档&#xff09;&#xff1a; --reset-offsets also has following scenarios to choose from (a…...

动漫设计属于什么专业/seo与sem的关系

NBA 史上连胜纪录 连胜场次 球队 年份 33 湖人 1971-72 20 雄鹿 1971 19 湖人 2000 18 公牛 1995-96 18 凯尔特人 1982 18 尼克斯 1969 17 太阳 2006 17 马刺 1996 17 凯尔特人 1959-60 17 华盛顿国会 1946 16 湖人 1999-2000 16 开拓者 1991 16 湖人 1991 16 雄鹿 1970 16 凯尔…...