版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第12章使用后台任务在Android系统中,Activity运行在称为UI线程的主线程中,并且系统对Activity的响应时间有严格的要求:对用户操作的响应时长不能超过规定的时限,否则系统将出现异常。因此,在Android系统中,对于需要使用较长时间执行的功能都应该放在后台进行。不仅如此,Android也要求对于需要使用较长时间或执行时间不确定的功能,例如网络通信等都必须放在后台执行。本章将对如何在后台执行程序任务进行介绍。目录12.1使用Java线程执行后台任务12.2课堂同步练习12.3使用Service执行后台任务12.4课堂同步练习12.1使用Java线程执行后台任务对于一些需要使用较长时间执行的程序任务,可以使用Java的线程机制,也就是Thread类来执行这些任务。下面通过一个简单的例子来看看如何使用Java的Thread类来执行后台任务:这个例子程序显示一个简单的时钟:程序每隔1秒实时显示系统日期时间,点击“停止”按钮将停止实时显示,并且按钮上的文字将变为“启动”,再次点击该按钮,又将实时显示系统日期时间。新建名为ch1201的工程。修改res/layout/activity_main.xml文件,使之显示一个TextView和一个Button,修改后的文件内容如下:<?xmlversion="1.0"encoding="utf-8"?><RelativeLayoutxmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"xmlns:tools="/tools"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity">
<TextViewandroid:id="@+id/id_textview"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:textSize="24sp"/>
<Buttonandroid:id="@+id/id_button"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:text="@string/text_button_stop"/>
</RelativeLayout>修改res/values/strings.xml文件,在其中定义在布局文件中用到的字符串引用,修改后的文件内容如下:<resources><stringname="app_name">ch1201</string>
<stringname="text_button_start">启动</string><stringname="text_button_stop">停止</string>
</resources>修改MainActivity.java文件,修改后的文件内容如下:packagecom.example.ch1201;
importandroid.os.Bundle;importandroid.os.Handler;importandroid.view.View;importandroid.widget.Button;importandroid.widget.TextView;
importandroidx.activity.EdgeToEdge;importandroidx.appcompat.app.AppCompatActivity;importandroidx.core.graphics.Insets;importandroidx.core.view.ViewCompat;importandroidx.core.view.WindowInsetsCompat;
importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.Locale;
publicclassMainActivityextendsAppCompatActivityimplementsView.OnClickListener{privateTextViewtv;privateButtonbtn;
privatebooleanstarted;privateHandlerhandler;
privateDated;privateSimpleDateFormatsdf;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main),(v,insets)->{InsetssystemBars=insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left,systemBars.top,systemBars.right,systemBars.bottom);returninsets;});
started=true;d=newDate();
tv=this.findViewById(R.id.id_textview);sdf=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss",Locale.CHINA);Stringds=sdf.format(d);tv.setText(ds);
btn=this.findViewById(R.id.id_button);btn.setOnClickListener(this);
handler=newHandler();Threadt=newThread(newMyTimer());t.start();}
privateclassMyTimerimplementsRunnable{@Overridepublicvoidrun(){while(started){try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}
handler.post(newRunnable(){@Overridepublicvoidrun(){d.setTime(System.currentTimeMillis());Stringds=sdf.format(d);tv.setText(ds);}});}}}
@OverridepublicvoidonClick(Viewv){Buttonb=(Button)v;
if(started){started=false;b.setText(R.string.text_button_start);}else{started=true;Threadt=newThread(newMyTimer());t.start();b.setText(R.string.text_button_stop);}}
}在类中定义了started变量,用于表示当前时钟是否正在运行,也就是是否在实时显示系统时间,同时,还定义了一个类型为Handler的变量handler,这个变量的作用是什么呢?这需要从Android的Activity的工作机制说起。Android的每个Activity都运行在自己独立的线程中,这个线程通常称为UI主线程,通过这个UI主线程Activity实现了与用户的交互。为了保证界面交互的实时性和不影响用户体验,Android规定Activity完成任何一次交互的时长都不能超过规定的时间(10秒),若超过这个时间,Android将会发生异常;同时,Android还规定,Activity界面上所显示的信息,只能由UI线程对其进行更新,其他任何线程都不能更新界面上所显示的信息,否则会发生异常。为了能够让其他非UI线程也能修改界面上的信息,Android为每个Activity提供了一个默认的消息队列,需要修改界面信息的线程通过这个消息队列向Activity发送消息进而由Activity的主界面UI线程来修改界面信息,Handler类就是为这个目的而设计的。12.2课堂同步练习将12.1节中的程序复制到你的开发环境中,并进行如下修改:每到一个整点,例如早上8:00或晚上10:00,系统自动播放一段简短的用于表示整点时间的音乐,就像整点的钟声。提示:每到一个整点,启动一个后台线程来播放音乐。12.3使用Service执行后台任务Service是Android的组件之一,它没有UI接口,使用Service可以完成一些需要长时间在后台运行的任务。Android的其他组件,如Activity,可以启动一个Service运行。Service一旦被启动,它将持续地在后台运行,直到被停止。Android提供两种类型的Service,分别为“启动式服务”和“绑定式服务”,在本节,只对启动式服务进行介绍。与Activity一样,Service也具有其固有的生命周期,通过重写这些生命周期回调函数来完成对Service的控制。Service的生命周期如图所示。在此需要强调的是,虽然Service是后台服务,但是,Service是运行在应用程序的主线程中的,也就是应用程序的UI线程中。Service是后台服务的含义是指:当启动Service的应用程序组件被停止后,Service仍然在运行。特别强调:因为Service运行在应用程序的主线程中,因此,在Service的各个回调函数中只能做一些简短的工作,对于需要长时间运行的任务则交给某个线程来运行。下面举一个例子说明Service的应用:使用Service在后台播放某个音乐。之所以使用Service播放音乐,是因为如果在Activity中播放音乐,当播放音乐的Activity退出时,音乐也将停止播放。如果希望退出Activity时,音乐还可以继续在后台播放,则需要使用Service服务。这个例子程序首先使用Activity来启动音乐的播放,当这个Activity退出时,系统将继续播放音乐,并在系统通知栏显示一个正在后台播放音乐的通知图标,点击这个图标,将再次打开控制Service的Activity界面,进而可控制音乐的播放。为此,新建名为ch1202的工程,修改res/layout/activity_main.xml布局文件,文件内容如下:<?xmlversion="1.0"encoding="utf-8"?><RelativeLayoutxmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"xmlns:tools="/tools"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity">
<Buttonandroid:id="@+id/id_button_1"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentTop="true"android:text="@string/text_button_1"/>
<Buttonandroid:id="@+id/id_button_2"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@id/id_button_1"android:text="@string/text_button_2"/>
<Buttonandroid:id="@+id/id_button_3"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@id/id_button_2"android:text="@string/text_button_3"/>
</RelativeLayout>修改res/values/strings.xml文件。<resources><stringname="app_name">ch1202</string>
<stringname="text_button_1">播放1号音乐</string><stringname="text_button_2">播放2号音乐</string><stringname="text_button_3">停止播放音乐</string>
</resources>编写用于播放音乐的Service程序代码。为了便于管理,在工程中新建名一个为com.example.ch1202.service的包,在这个包下新建一个MyService类,修改MyService.java文件内容为如下代码:packagecom.example.ch1202.service;
importandroid.app.Notification;importandroid.app.NotificationChannel;importandroid.app.NotificationManager;importandroid.app.PendingIntent;importandroid.app.Service;importandroid.content.Intent;importandroid.content.res.AssetFileDescriptor;importandroid.content.res.AssetManager;importandroid.media.AudioManager;importandroid.media.MediaPlayer;importandroid.os.Build;importandroid.os.Environment;importandroid.os.IBinder;importandroid.widget.Toast;
importcom.example.ch1202.MainActivity;importjava.io.IOException;publicclassMyServiceextendsService{privateMediaPlayermp;
privateNotificationManagernotificationManager;privatefinalintmNOTIFICATION=(int)System.currentTimeMillis();
@OverridepublicvoidonCreate(){mp=null;notificationManager=(NotificationManager)getSystemService(NOTIFICATION_SERVICE);showNotification();System.out.println("onCreatecalled");}
@OverridepublicintonStartCommand(Intentintent,intflags,intstartId){System.out.println("onStartCommandcalled");
if(mp!=null){if(mp.isPlaying()){mp.stop();}mp.release();}
mp=newMediaPlayer();mp.setAudioStreamType(AudioManager.STREAM_MUSIC);mp.setOnPreparedListener(newMediaPlayer.OnPreparedListener(){@OverridepublicvoidonPrepared(MediaPlayermp){mp.start();}});mp.setOnErrorListener(newMediaPlayer.OnErrorListener(){@OverridepublicbooleanonError(MediaPlayermp,intwhat,intextra){Toast.makeText(MyService.this,"无法播放该文件",Toast.LENGTH_LONG).show();returnfalse;}});mp.setOnCompletionListener(newMediaPlayer.OnCompletionListener(){@OverridepublicvoidonCompletion(MediaPlayermp){MyService.this.stopSelf();}});
try{Stringmusic=intent.getStringExtra("music");AssetManageram=this.getAssets();assertmusic!=null;AssetFileDescriptoraf=am.openFd(music);mp.setDataSource(af);mp.prepareAsync();}catch(IllegalArgumentException|IOException|IllegalStateException|SecurityExceptione){e.printStackTrace();}
returnSTART_STICKY;}
@OverridepublicIBinderonBind(Intentintent){//由于我们不提供绑定式服务,因此,直接返回nullreturnnull;}
@OverridepublicvoidonDestroy(){if(mp!=null){if(mp.isPlaying()){mp.stop();}mp.release();}
notificationManager.cancel(mNOTIFICATION);System.out.println("onDestroycalled");}privatevoidshowNotification(){PendingIntentcontentIntent=PendingIntent.getActivity(this,0,newIntent(this,MainActivity.class),PendingIntent.FLAG_IMMUTABLE);
CharSequencetext="PlayingMusic";Notification.Builderbuilder=null;if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){builder=newNotification.Builder(this,"MUSIC");}else{builder=newNotification.Builder(this);}builder.setContentTitle(text);builder.setContentText(text+"...");builder.setSmallIcon(android.R.drawable.ic_media_play);builder.setContentIntent(contentIntent);//执行intentNotificationnotification=builder.build();NotificationChannelchannel=null;if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){channel=newNotificationChannel("MUSIC","测试渠道名称",NotificationManager.IMPORTANCE_DEFAULT);notificationManager.createNotificationChannel(channel);}notificationManager.notify(mNOTIFICATION,notification);}}还需要特别说明onStartCommand()回调函数的返回值的含义。onStartCommand()函数的返回值只能是如下的几个值之一,这些值及其含义如下。(1)START_NOT_STICKY这个返回值告诉系统,当系统资源紧缺时,在onStartCommand()函数返回后,如果系统结束了这个服务,则不需要重新创建和启动这个服务。(2)START_STICKY这个返回值告诉系统,当系统资源紧缺时,在onStartCommand()函数返回后,如果系统结束了这个服务,则需要重新创建和启动这个服务,并且再次使用Intent为null的参数调用服务的onStartCommand()回调函数。(3)START_REDELIVER_INTENT这个返回值告诉系统,当系统资源紧缺时,在onStartCommand()函数返回后,如果系统结束了这个服务,则需要重新创建和启动这个服务,并且使用之前调用onStartCommand()时的Intent参数再次调用服务的onStartCommand()回调函数。现在修改MainActivity类,修改后的程序代码如下:packagecom.example.ch1202;
importandroid.Manifest;importandroid.content.Intent;importandroid.content.pm.PackageManager;importandroid.os.Build;importandroid.os.Bundle;importandroid.view.View;importandroid.widget.Button;
importandroidx.activity.EdgeToEdge;importandroidx.appcompat.app.AppCompatActivity;importandroidx.core.graphics.Insets;importandroidx.core.view.ViewCompat;importandroidx.core.view.WindowInsetsCompat;
importcom.example.ch1202.service.MyService;
publicclassMainActivityextendsAppCompatActivityimplementsView.OnClickListener{
@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main),(v,insets)->{InsetssystemBars=insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left,systemBars.top,systemBars.right,systemBars.bottom);returninsets;});
Buttonbtn1=this.findViewById(R.id.id_button_1);btn1.setOnClickListener(this);Buttonbtn2=this.findViewById(R.id.id_button_2);btn2.setOnClickListener(this);Buttonbtn3=this.findViewById(R.id.id_button_3);btn3.setOnClickListener(this);
}@OverridepublicvoidonClick(Viewv){if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.TIRAMISU){if(checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)!=PackageManager.PERMISSION_GRANTED){requestPermissions(newString[]{Manifest.permission.POST_NOTIFICATIONS},1000);}}
intid=v.getId();
if(id==R.id.id_button_1){Intentservice=newIntent(this,MyService.class);service.putExtra("music","ring01.wav");this.startService(service);}elseif(id==R.id.id_button_2){Intentservice=newIntent(this,MyService.class
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 混凝土运输时效预警方案
- 加油站钢柱基础预埋方案
- 高龄患者肠道准备质量与息肉漏诊率的关系
- 高风险妊娠自身免疫疾病筛查流程
- 高端设备配置与医疗质量关联的机制研究
- 高端影像设备招标中的技术评分标准构建
- 幼儿园家长育儿讲座签到率与留存率-基于2024年讲座视频观看后台数据
- 幼儿园户外活动时间保障对幼儿体质发展影响-基于2024年活动时间与体质测试关联
- 2026年江苏省苏锡常镇四市2026届高三下学期5月二模试题 物理 含答案新版
- 初中英语期末考题与解析
- 岗位系数说明正式公示版
- 血液透析室(中心)的人员配置及职责
- 第四种检查器介绍
- BB/T 0066-2017聚乙烯挤出发泡包装材料
- 马克思主义基本原理第一章案例
- 07.2五年级下册道德与法治第7课《不甘屈辱 奋勇抗争》PPT教学课件(第二课时)
- 安全生产责任保险制度解读与推行
- 变电站工程构架吊装方案
- 马克思主义基本原理概论:5.3 资本主义的历史地位和发展趋势
- 全国28个省、直辖市、自治区革命老区县市名单
- 身份证标志台帐
评论
0/150
提交评论