🌷🍁 博主 libin9iOak带您 Go to New World.✨🍁
🦄 个人主页——libin9iOak的博客🎐
🐳 《面试题大全》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺
🌊 《IDEA开发秘籍》学会IDEA常用操作,工作效率翻倍~💐
🪁🍁 希望本文能够给您带来一定的帮助🌸文章粗浅,敬请批评指正!🍁🐥
《移动互联网技术》课程简介
《移动互联网技术》课程是软件工程、电子信息等专业的专业课,主要介绍移动互联网系统及应用开发技术。课程内容主要包括移动互联网概述、无线网络技术、无线定位技术、Android应用开发和移动应用项目实践等五个部分。移动互联网概述主要介绍移动互联网的概况和发展,以及移动计算的特点。无线网络技术部分主要介绍移动通信网络(包括2G/3G/4G/5G技术)、无线传感器网络、Ad hoc网络、各种移动通信协议,以及移动IP技术。无线定位技术部分主要介绍无线定位的基本原理、定位方法、定位业务、数据采集等相关技术。Android应用开发部分主要介绍移动应用的开发环境、应用开发框架和各种功能组件以及常用的开发工具。移动应用项目实践部分主要介绍移动应用开发过程、移动应用客户端开发、以及应用开发实例。
课程的教学培养目标如下:
1.培养学生综合运用多门课程知识以解决工程领域问题的能力,能够理解各种移动通信方法,完成移动定位算法的设计。
2.培养学生移动应用编程能力,能够编写Andorid应用的主要功能模块,并掌握移动应用的开发流程。
3. 培养工程实践能力和创新能力。
通过本课程的学习应达到以下目的:
1.掌握移动互联网的基本概念和原理;
2.掌握移动应用系统的设计原则;
3.掌握Android应用软件的基本编程方法;
4.能正确使用常用的移动应用开发工具和测试工具。
第八章 消息与服务
本章小结:
1、本单元学习目的**
通过学习Android系统的广播机制,掌握异步执行和同步执行两种广播接收方式,通知的发送和处理方式,理解Intent和PendingIntent的区别。重点掌握异步消息处理机制,两种消息编程模型:Handler和AsyncTask,以及在后台执行任务的Service组件。
2**、本单元学习要求**
(1) 掌握不同类型广播监听方式,以及创建通知的方法;
(2) 掌握PendingIntent使用方法;
(3) 理解异步处理和同步处理的联系和区别。
3**、本单元学习方法**
结合教材以及Android Studio开发软件,对广播、通知、PendingIntent、Handler、AsyncTask和Service等组件进行编程练习,运行调试,并在模拟器中观察运行情况。
4**、本单元重点难点分析**
重点
(1)广播机制
系统发送消息通常采用广播的方式。应用要接收系统发送的消息,就像打开一个收音机,然后收听这些广播,从广播获取系统的各种状态信息,比如接听到一个电话、收到一条短信、获取手机开机信息等等。
Android 系统采用观察者模式实现消息发送和接收。每个应用首先向系统注册自己关心的广播消息,就像很多新闻类APP,用户喜欢体育频道就加上关注,当有新的体育消息时,APP就会将消息推送到屏幕上。系统是广播消息的主要来源,此外应用程序也可以发送广播,即可以在应用间发送,也可以在应用内部发送。
要接收广播,先要注册广播,让系统知道应用程序对哪些信息感兴趣。一旦系统有了应用程序感兴趣的信息,它就通过回调的方式把消息发送给应用程序。就像你想知道一场比赛的结果,你把你的电话告诉去比赛现场的朋友,有新的比赛消息的时候,他就可以打电话告诉你比赛进展。你的电话号码就是一个回调函数接口。
注册广播一般有两种方式:静态注册和动态注册。静态注册是在AndroidManifest.xml中配置标签。下面采用静态注册的方式来接收系统的开机启动消息。
<uses-permission Android:name= "Android.permission.RECEIVE_BOOT_COMPLETED" /> <receiver <!-- 在BootCompleteReceiver类中接收广播 --> android:name=".**BroadcastReceiver.BootCompleteReceiver**" android:enabled="true" android:exported="true"> <Intent-filter> <action android:name="**android.intent.action.BOOT_COMPLETED**" /> </Intent-filter> </receiver>
广播接收器是一个自定义类:BootCompleteReceiver。android:enabled="true"表示是否启用这个广播接收器;android:exported="true"表示这个广播接收器能否接收其他APP发出的广播。在标签中加入想要收听的广播消息,即Android系统启动完成后会发出的android.intent.action.BOOT_COMPLETED广播。
向系统注册了要收听的消息,接下来需要在收听到消息后对消息进行处理。广播接收器BootCompleteReceiver从BroadcastReceiver类继承。重写BroadcastReceiver类的回调函数onReceive()。作为演示,下面只是通过日志和Toast来显示收到了系统的开机消息。当然可以在这里实现更复杂和更适用的功能,比如启动一个音乐播放服务。
public class BootCompleteReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent Intent) { Log.i(TAG, “开机启动. 启动MsgService..."); Toast.makeText(context, "开机启动",Toast.LENGTH_LONG).show(); } }
动态注册是在代码中注册要收听的广播。下面以接收“网络状态变化”广播为例,说明如何动态注册广播。首先,定义IntentFilter,给它加入一个action:android.net.conn.CONNECTIVITY_CHANGE(网络连接状态改变)。接着,创建广播接收器NetworkChangeReceiver,把接收器和动作过滤器通过registerReceiver 函数绑定在一起,完成动态注册。
public class BroadcastActivity extends AppCompatActivity { NetworkChangeReceiver netChangeReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_broadcast); IntentFilter netConnIntentFilter = new IntentFilter(); netConnIntentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); netChangeReceiver = new NetworkChangeReceiver(); registerReceiver(netChangeReceiver, netConnIntentFilter); } // 在销毁的时候,要把注册取消。 @Override protected void onDestroy() { super.onDestroy(); if (netChangeReceiver != null) unregisterReceiver(netChangeReceiver); } }
NetworkChangeReceiver监听到网络状态发生变化,在onReceive函数中进行处理。通过context对象的getSystemService函数获取连接管理器,由管理器来获得当前网络状态的各项信息,并根据networkInfo判断当前网络是否连通。
public class NetworkChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent Intent) { ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isAvailable()) { Toast.makeText(context, "现在可以上网", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, "网络无法连通", Toast.LENGTH_SHORT).show(); } } }
注意还要声明权限:
要测试以上代码,可以在模拟器中打开Settings界面,启动和禁用网络。这里有几个问题值得思考:开机启动使用动态注册还是静态注册?网络状态又选用哪种注册方式?为什么要这样选择?
按照广播的发布方式,Android系统提供了两种广播:普通广播(Normal Broadcast,又称为标准广播)和有序广播(Ordered Broadcast)。标准广播与收听的校园广播类似,广播站广播消息,每个人都能听到。标准广播的消息几乎同时到达每一个接收器,它们没有接收先后顺序之分。消息不会被其他人屏蔽,每个人都能够收到系统消息。这种广播方式也称为完全异步执行的广播。
采用有序广播,接收方在接收消息时有时间上的先后顺序。系统发出广播,消息到达A、再到B、最后到达C。这种方式也称为同步执行的广播。使用有序广播,在某一个时刻只有一个接收器收到消息,它处理完消息以后,再把消息发送给下一个接收器。收听广播的顺序由接收器的优先级来确定。接收器可以截断消息,不传递,这样后面的接收器就无法获得广播消息。
广播是一种可以跨进程的通信方式。应用程序的发送广播消息,其他应用程序也可以收到。广播的消息由Intent来传递。首先,定义了一个MY_BROADCAST动作,然后调用sendBroadcast函数将Intent发送出去,这就生成了广播消息。
Intent Intent = new Intent(“pers.cnzdy.mobilerookie.MY_BROADCAST”)
sendBroadcast(Intent);
接收器与前面给出的代码一样,是自定义的接收器MyBroadcastReceiver,同样要重写onReceive 函数。
public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent Intent) { … … } }
采用静态方式注册接收器MyBroadcastReceiver,设置接听action:MY_BROADCAST。
<receiver Android:name=".MyBroadcastReceiver"> <Intent-filter> <action Android:name="pers.cnzdy.mobilerookie.MY_BROADCAST"/> </Intent-filter> </receiver>
如果要发送有序广播,则需要调用sendOrderedBroadcast() 函数,函数的第二个参数是一个与权限相关的字符串,可以直接传入null。如果想截断广播,可以在onReceive 函数中调用abortBroadcast 函数,以阻止消息继续传播。
Intent Intent = new Intent(“pers.cnzdy.mobilerookie.MY_BROADCAST”);
sendOrderedBroadcast(Intent, null);
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent Intent) {
… …
abortBroadcast();
}
}
在应用程序间直接广播消息会有一些问题:第一,广播数据被截获可能存在安全问题;第二,应用程序可能收到大量的垃圾消息。针对这些问题,Android还提供了另外一种广播方式:本地广播(Local Broadcast)。本地广播只能在应用内部传递,并且只有应用程序自身能够接收。
发送本地广播要用到本地广播管理器LocalBroadcastManager,同样还需要构造Intent,接着通过localBroadcastManager调用sendBroadcast函数来发送消息。
localBroadcastManager = LocalBroadcastManager.getInstance(this);
Intent Intent = new Intent(“pers.cnzdy.mobilerookie.LOCAL_BROADCAST”);
localBroadcastManager.sendBroadcast(Intent);
注册本地广播需要通过LocalBroadcastManager进行动态注册。静态注册是为了让程序在未启动的情况下也能收到广播;而发送本地广播时,由于程序已经启动,因此不需要使用静态注册功能。
IntentFilter = new IntentFilter();
IntentFilter.addAction(“pers.cnzdy.mobilerookie.LOCAL_BROADCAST”);
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, IntentFilter);
// 取消本地广播注册
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
(2)通知管理
在系统状态栏上展示的消息称为“通知”。通知需要通过NotificationManager(通知管理者)来发送。创建通知就像在办公室发布通知一样,先撰写通知的标题、通知的内容、通知的日期等等,然后再发送出去。使用Andoird应用程序能够创建更具视觉效果的通知消息。
通过调用 Context 的getSystemService 函数获取NotificationManager对象;然后调用它的notify函数发送通知。notify 函数有两个参数,第一个参数是通知的id,是保证通知唯一性的编号,第二个参数是通知对象。
NotificationManager manager = (NotificationManager) getSystemService (NOTIFICATION_SERVICE); // 设置标题、内容、创建时间、显示通知的大小图标,最后创建通知。 Notification notification = new NotificationCompat.Builder(this) .setContentTitle("新的测试题目") .setContentText("Android应用界面中有哪两种类型的视图组件?") .setWhen(System.currentTimeMillis()) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) .build(); manager.notify(1, notification);
notify发送的通知将显示在系统状态栏。现在点击这个通知,但是没有任何响应,这是因为还没有实现点击处理。
(3)Handler和AsyncTask****工作流程
- Handler
在主活动MainActivity中,首先启动一个子线程来完成一些耗时的运算或I/O处理,比如执行大数据运算、下载多个图片文件、完成复杂的图像处理等等。耗时任务结束后,创建一个Message对象,然后通过Handler将Message发送出去。
new Thread(new Runnable() {
@Override
public void run() {
… …
Message msg = new Message();
// 通知更新界面
msg.what = UPDATE_UI;
handler.sendMessage(msg);
}
}).start();
接下来更新界面。在主活动中创建handler对象,构造一个handleMessage函数来处理子线程发来的消息。根据接收到的消息类别来完成相应的工作,比如在界面上显示运算的完成进度。
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_UI:
textView.setText(“运算完成进度50%");
break;
default:
break;
}
}
};
Handler的运行机制:首先,在主线程中创建一个Handler 对象;接着Looper从消息队列中取出队列头部的消息,然后分发消息;Handler处理收到的消息,并调用handleMessage函数更新界面。如果子线程需要进行界面操作时,就创建一个Message 对象,并通过Handler将这条消息发送到消息队列中。
Handler的各个组件相互关联。Handler实现了handleMessage接口,这个接口用来处理各种接收到的消息。此外,Handler还指向一个消息队列和一个Looper对象。Looper拥有消息队列,并且它启动线程来处理消息循环。
消息队列由多个消息(Message)对象构成。当需要发送Message时,可以通过new Message()创建一个Message实例。Android推荐通过Message.obtain或Handler.obtainMessage函数来获取Message对象,即:首先在消息池中查看是否有可用的Message实例,如果存在Message则直接取出并返回消息实例。
Handler的异步消息处理流程如下:
a) 在主线程中创建一个Handler对象,并重写handleMessage()方法;
b) 子线程需要进行UI操作时,创建一个Message对象,并通过Handler将这条消息发送出去;
c) 更新界面的消息被添加到MessageQueue中等待被处理;
d) Looper从MessageQueue中取出待处理消息,分发到Handler的handleMessage()函数中。
e) Handler的handleMessage()函数处理接收到的消息,通过消息更新界面。
总的来说,如果需要执行耗时的操作,例如从互联网上下载数据,或者在本地读取一个很大的文件时,不能把这些操作放在主线程中,应该在一个子线程中执行耗时任务。如果子线程要对界面进行更新,比如提示执行进度,则必须通过主线程来更新界面。Handler运行在主线程(UI线程)中,它与子线程通过Message对象来传递消息。Handler接收子线程传递的(子线程用sendMessage函数传递)Message对象,并把这些消息放入主线程队列中,配合主线程更新界面。
2) AsyncTask
AsyncTask是一个抽象类,使用时需要自定义一个继承AsyncTask的异步处理类。AsyncTask的泛型参数指示异步任务中各种参数的类型,这些参数包括:Params表示给后台任务传递的参数;Progress是当前任务的执行进度,可以在界面上显示;Result指示任务完成后返回的结果。
public class MainActivity extends Activity {
private TimeConsumingTask mTask;
// 用进度条控件来显示当前任务的执行进度。
private ProgressBar progressBar;
private TextView textView;
private class TimeConsumingTask extends AsyncTask<Params, Progress, Result>
{
… …
}
}
自定义TimeConsumingTask类继承AsyncTask,重写onPreExecute 函数,执行界面初始化操作,并且在textView控件上显示任务已启动。onPostExecute函数是在任务完成后执行界面操作,也是通过文本形式显示执行结果。
private class TimeConsumingTask extends AsyncTask<String, Integer, String> {
@Override
protected void onPreExecute() {
textView.setText(“启动…”);
}
@Override
protected void onPostExecute(String result) {
textView.setText(result);
}
}
重写doInBackground函数,它用于执行耗时任务;同时通过publishProgress函数发布任务进度信息。
@Override
protected String doInBackground(String… params) {
int percent = doTimeConsumingTask();
publishProgress(percent);
return result;
}
doInBackground本身不能执行界面操作,需要在onProgressUpdate函数中更新界面上的进度显示。
@Override
protected void onProgressUpdate(Integer… progresses) {
// 操作界面上控件
progressBar.setProgress(progresses[0]);
textView.setText(“执行进度…" + progresses[0] + “%”);
}
将Handler和AsyncTask做一个对比。AsyncTask的异步操作都在自己的类中完成,通过接口提供进度反馈。Handler需要在主线程中启动子线程,然后通过handler来连接子线程和活动界面。对于单个异步任务,AsyncTask更简单,如果要处理多个异步任务就比较复杂。Handler正好相反,从单个任务来看代码多,结构复杂,而在处理多个后台任务时,相比AsyncTask,实现更容易。AsyncTask比Handler更耗资源,适合简单的异步处理。
(4)后台服务
Service没有用户界面,它的职责就是在后台执行操作。当用户切换到另外的应用场景时,Service仍然持续在后台运行。但是,服务离不开应用程序,当某个应用程序进程被杀掉时,所有依赖于该进程的服务也会停止运行。就像音乐播放器,你可以切换到其他应用软件,比如用QQ聊天,这时音乐仍然在后台播放。当播放器关闭后,后台服务就不再播放音乐。
Service是实现程序后台运行的解决方案,适合于执行不需要和用户交互且长期运行的任务。服务运行不依赖于任何用户界面,当程序被切换到后台或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。服务并不是运行在一个独立的进程中,而是依赖于创建服务的应用程序进程。
下面创建一个音乐服务,它在后台运行。每次服务启动都会调用onStartCommand 函数。
public class MusicService extends Service {
@Override
public IBinder onBind(Intent Intent) { return null; }
@Override
public void onCreate() { super.onCreate(); }
@Override
public int onStartCommand(Intent Intent, int flags, int startId) {
return super.onStartCommand(Intent, flags, startId);
}
@Override
public void onDestroy() { super.onDestroy(); }
}
要启动一个服务可以在活动(MainActivity)中通过Intent来实现。通过调用startService开启服务,调用stopService停止服务。
Intent startIntent = new Intent(this, MusicService.class);
startService(startIntent);
Intent stopIntent = new Intent(this, MusicService.class);
stopService(stopIntent);
服务和活动一样,都要在配置文件中进行注册。
<application
……
调用startService函数后,服务就开始运行。服务运行期间,启动它的活动可能被销毁,但是服务仍然可以存在,只要整个应用不退出运行。服务通常用来完成简单任务,因此不返回结果。
定义一个绑定对象binder,binder对象提供了查看进度的函数:getProgress函数。
public class MusicService extends Service {
private MusicBinder binder = new MusicBinder();
class MusicBinder extends Binder {
public void start() {
Log.d(TAG, “执行”);
}
public int getProgress() {
Log.d(TAG, “播放进度");
return 0;
}
}
@Override
public IBinder onBind(Intent Intent) { return binder; }
}
定义了MusicService以后,在活动MusicActivity中启动服务。首先,定义服务连接对象;然后在onServiceConnected 函数中获取binder对象,通过binder来启动MusicService。
public class MusicActivity extends Activity {
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
binder = (MusicService.MusicBinder) service;
binder.start ();
binder.getProgress();
}
@Override
public void onServiceDisconnected(ComponentName name) { }
};
服务一般不返回结果,但有时候也希望服务能给出反馈信息,这时可以使用bindService 函数来实现活动与服务之间的通信。绑定以后,服务提供一个组件与Service交互的接口,通过它可以发送请求、返回结果,实现跨进程通信;并且多个组件也可以共用一个服务。
要绑定服务,首先定义Intent,然后调用bindService 函数。bindService的第一个参数是 Intent 对象,第二个参数是ServiceConnection 对象,第三个参数是一个标志位,比如BIND_AUTO_CREATE 表示服务会在绑定后自动创建,这样就会触发调用音乐服务中的 onCreate 函数,但onStartCommand 函数不会执行。unbindService 函数解除服务绑定,同时Service也被销毁。
Intent bindIntent = new Intent(this, MusicService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE);
unbindService(connection);
Android系统的Service有两种启动方式:startService(启动服务)和bindService(绑定服务)。如果服务之前没有创建过,startService和bindService都会先调用onCreate 函数来创建服务。接下来,对应onStartCommand函数的是onBind函数。服务启动后会一直保持运行。对于绑定服务来说,执行onBind 函数会返回 IBinder 对象,这样活动就能通过一个IBinder接口与服务进行通信。采用startService,可以让服务自动停止或者强制让它停止,即调用stopSelf函数或者其他组件调用stopService函数来停止它。对于绑定服务,调用unbindService 函数关闭连接,执行onDestroy 函数,服务被销毁。启动服务一旦开启Service,启动者(Activity)与Service之间将不存在任何联系,即使启动者销毁,服务仍然处于活动状态。绑定服务的启动者与Service相关联,一旦启动者销毁,那么Service也将随之销毁。另外,一个Service可以同时和多个组件绑定,当多个组件都解除绑定之后,系统会销毁Service。
难点
(1)异步消息处理机制
第一种方法,通过继承Thread类来创建线程。首先,自定义线程类TaskThread,然后重写run函数,并且在主程序中创建TaskThread线程对象,然后调用start函数启动线程。
class TaskThread extends Thread {
@override
// 运行在子线程中
public void run() {
// 运行需要耗时的程序
}
}
TaskThread taskThread = new TaskThread()
taskThread.start();
第二种方法,实现Runnable接口也可以创建线程。创建一个线程对象thread,把实现Runnable接口的taskThread传给它,然后再启动线程。
class TaskThread implements Runnable {
@override
public void run() {
… …
}
}
TaskThread taskThread = new TaskThread();
Thread thread = new Thread(taskThread)
thread.start();
第三种方法,采用匿名类的方式来启动线程。
new Thread(new Runnable() {
@Override
public void run() {
… …
}
}).start(); // 启动匿名类线程
在Android系统中,有时候需要执行某些耗时任务,比如在子线程中下载图片,同时把下载情况显示在界面上。在下面的例子中,主界面MainActivity创建了一个线程,线程完成一个耗时的运算,接下来要把运算的结果显示在界面上,如果直接在线程中调用textView.setText函数,这时Android系统会报错,这是因为Android系统不允许在子线程中进行UI操作。
new Thread(new Runnable() {
@Override
public void run() {
… …
float result = compute();
// Android系统不允许在子线程中直接操作界面控件。
textView.setText(“运算完成:“ + result.toString());
}
}).start();
解决子线程与界面交互的办法是采用异步消息处理机制。Android系统提供了异步处理的Handler机制。异步消息处理机制把耗时运算和界面操作分离开,Handler运行在界面线程(也就是UI线程)中;执行运算的子线程不直接与界面联系,它通过发送消息的方式(Message对象)将结果传递给Handler;Handler在接收到消息以后,把消息放入主线程队列中,并且配合主线程更新界面。
(2) PendingIntent 与Intent
采用Intent无法实现通过点击通知来打开活动界面,因为使用Intent时系统会马上执行“意图”,并启动活动界面(执行action)。而收到通知时,用户不会立刻打开通知对应的活动界面,他们可以选择在任何时间来查看。因此,要实现通知点击处理,需要用到另外一个意图:PendingIntent。PendingIntent提供了延迟执行的方式,可以在任何选定的时间去执行某个动作的 Intent。
Intent是立即执行某个动作,PendingIntent是延迟执行,它更加倾向于在某个合适的时机去执行某个动作。PendingIntent是一种特殊的异步处理机制,它由AMS(Activity Manager Service)管理。在Android系统中,活动管理服务AMS是最核心的服务,它负责系统四大组件的启动、切换、调度以及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块类似。
通过PendingIntent的getActivity函数获取PendingIntent对象。getActivity函数的第一个参数是 Context对象;第二个参数一般用不到,传入 0;第三个参数是 Intent 对象,通过这个对象来创建 PendingIntent 对象;第四个参数用来确定 PendingIntent 的行为,一共有四种选项,分别是:FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT 和 FLAG_UPDATE_CURRENT。FLAG_CANCEL_CURRENT表示如果PendingIntent已经存在,那么当前的PendingIntent会取消掉;其他选项可以查阅Android开发文档。
Intent Intent = new Intent(this, QuizActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, Intent,
PendingIntent.FLAG_CANCEL_CURRENT);
// 利用PendingIntent创建通知
NotificationManager manager = (NotificationManager)
getSystemService (NOTIFICATION_SERVICE);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle(“新的测试题目”)
… …
.setContentIntent(pi)
.build();
manager.notify(1, notification);
在创建通知的时候,需要调用setContentIntent函数设置 PendingIntent 对象,再由PendingIntent对象来启动通知对应的活动。 然后再调用NotificationManager 的 cancel 方法就可以取消通知:
manager.cancel(1);
cancel(1)中的“1”是发送通知指定的 id 号,即:manager.notify(1, notification)中的第一个参数。如果想要取消某个特定的通知,就在cancel 函数中传入该通知的 id号。
通知还有更丰富的形式,比如收到通知的时候,播放一段声音,这样用户就知道有通知消息了。在定义通知的时候,加入setSound函数,选择手机音频目录下的已有音频文件来播放特定的声音。另外,还可以设置震动方式来提醒用户。
Notification notification = new NotificationCompat.Builder(this)
… …
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher))
.setContentIntent(pi)
.setAutoCancel(true)
.setSound(Uri.fromFile(new File(
“/system/media/audio/ringtones/Andromeda.ogg”)))
.setVibrate(new long[] {0, 500, 500, 1000})
.build();
setVibrate函数设置的震动参数是时间,单位是毫秒。“0”表示手机静止的时间,第一个“500”表示手机振动的时间,第二个“500”表示震动后接下来手机静止的时间,就这样静止时间、震动时间交错定义。注意震动需要设置权限:
(3)IntentService
为了实现服务的前台运行,先创建通知对象,然后再调用startForeground 函数。startForeground的第一个参数是通知的编号,第二个参数是已经创建的通知对象。调用 startForeground 函数后会让音乐服务变为一个前台服务,显示系统状态栏上。
Intent Intent = new Intent(this, MusicActivity.class);
Pendinglntent pi = Pendinglntent.getActivity(this, 0, Intent, 0);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle(“音乐")
.setContentText(“成都")
.setWhen(System.currentTimeMillis())
… …
.setContentlntent(pi)
.build();
startForeground(1, notification);
前台服务与普通服务区别是:前台服务显示在系统状态栏上,表示服务正在运行;并且用户可以查看服务运行的详细信息,类似于通知的显示。
服务没有自己的进程,它和活动一样都运行在当前进程的主线程中;因此大运算量的任务不能在服务中运行,否则会影响界面主线程。尝试一下在服务中执行多重循环的耗时操作,这时系统会提示ANR(Application Not Response)警告,表示大运算占据了界面线程,现在应用无法做出响应。
如果要在服务中完成耗时操作,需要在服务中启动一个单独的工作线程;同时,需要调用stopSelf 函数,以便在任务完成以后服务能够自动停止。
@Override
public int onStartCommand(Intent Intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
doTimeConsumingTask();
stopSelf();
}
}).start();
return super.onStartCommand(Intent, flags, startId);
}
经常会有一些程序员在服务中编写耗时任务,而又忘记了开启线程,或者忘记了调用 stopSelf 方法,这样就会引起程序报错。为了简化耗时服务的编写。Android提供了IntentService作为一个简单、异步、会自动停止的服务。只需要继承IntentService类,并且重写onHandleIntent 函数;在onHandleIntent中处理耗时的任务,就不用担心 ANR问题,因为这个函数本身就在子线程中运行。
public class MusicIntentService extends IntentService {
public MusicIntentService() { super(“MusicIntentService”); }
@Override
protected void onHandleIntent(Intent Intent) {
doTimeConsumingTask();
}
@Override
public void onDestroy() { super.onDestroy(); }
}
IntentService创建一个异步、会自动停止的服务;然后将请求的Intent加入队列,通过内部的工作线程来完成请求的任务。每一个请求都会在一个单独的工作线程中进行处理。工作线程与主线程分离,相互之间不影响,不会造成应用无法响应的问题。
本章习题:
1、本单元考核点
Android的广播机制。
通知的发送和处理方式。
异步消息处理机制,Handler和AsyncTask的运行机制和使用方法。
Service的不同使用方式和具体应用。
2、本单元课后习题
1、Android单线程模型主要缺点是什么?有何解决方案?
答案:(1)单线程模型中,如果所有操作都在主线程执行,可能导致运行性能非常差,比如访问网络或数据库之类的耗时操作将导致所有的 UI 事件不能分发,用户界面反应迟钝,由于 Android 对应用响应有着严格的时间要求,当应用程序响应时间超过5秒时,系统就会弹出应用程序无响应的警告信息对话框,造成程序崩溃,严重影响用户体验。
(2)解决方案:一定要保证主线程( UI 线程)不被阻塞,对于耗时操作,需要把它放到一个单独的后台工作(worker)线程中执行。
2、 下面是Android消息(Message)处理机制中工作线程片断,请根据注释提示完成空白划线处所需代码。
public void run() {
for (int i = 0; i < 10; i++) {
… …
try {
… …
} catch (InterruptedException e) {
Message msg = handler.obtainMessage();
//创建一个Bundle对象,用于存放出错信息
(1)___________________________________________________;
b.putInt(“state”, STATE_ERROR);
//将信息包b放入消息对象msg中
(2)___________________________________________________;
//由handler对象将消息msg发送到消息队列中
(3)___________________________________________________;
}
}
}
答案:
public void run() {
for (int i = 0; i < 10; i++) {
… …
try {
… …
} catch (InterruptedException e) {//线程休眠异常中断时
Message msg = handler.obtainMessage();
//创建一个Bundle对象,用于存放出错信息
Bundle b = new Bundle();
//将整型常量STATE_ERROR以键名“state”放入b对象中
b.putInt(“state”, STATE_ERROR);
//将信息包b放入消息对象msg中
msg.setData(b);
//由Handler对象将消息发送到消息队列中
handler.sendMessage(msg);
}
}
}
3、 BroadcastReceiver作为应用级组件必须经过注册才能处理广播消息,注册有哪两种方式?
答案:
(1)静态注册:在 AndroidManifest.xml 中用<receiver>
标签声明,并在节点辖域中用 <intent-filter>
标签设置过滤器;(2)动态注册:在代码中定义并设置好一个 IntentFilter 对象,然后在需要注册的地方调用 Context.registerReceiver(BroadcastReceiver, intentFilter) 方法,撤消注册时,调用 Context.unregisterReceiver(BroadcastReceiver ) 方法。动态注册的 Context 对象被销毁时, BroadcastReceiver 也随之自动注销。
参考资源:
1、移动开发_智能移动终端应用开发技术文章 - 红黑联盟:https://www.2cto.com/kf/yidong/
2、Android开发 - 网站分类 - 博客园:https://www.cnblogs.com/cate/android/
原创声明
=======
作者: [ libin9iOak ]
本文为原创文章,版权归作者所有。未经许可,禁止转载、复制或引用。
作者保证信息真实可靠,但不对准确性和完整性承担责任。
未经许可,禁止商业用途。
如有疑问或建议,请联系作者。
感谢您的支持与尊重。
点击
下方名片
,加入IT技术核心学习团队。一起探索科技的未来,共同成长。