《移动互联网技术》第八章 消息与服务:掌握不同类型广播监听方式,以及创建通知的方法

简介: 《移动互联网技术》第八章 消息与服务:掌握不同类型广播监听方式,以及创建通知的方法

🌷🍁 博主 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)HandlerAsyncTask****工作流程

  1. 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技术核心学习团队。一起探索科技的未来,共同成长。


目录
相关文章
|
7月前
|
设计模式 Java Spring
【Spring源码】WebSocket做推送动作的底层实例是谁
我们都知道WebSocket可以主动推送消息给用户,那做推送动作的底层实例究竟是谁?我们先整体看下整个模块的组织机构。可以看到handleMessage方法定义了每个消息格式采用不同的消息处理方法,而这些方法该类并**没有实现**,而是留给了子类去实现。
【Spring源码】WebSocket做推送动作的底层实例是谁
|
7月前
|
消息中间件 Java RocketMQ
MQ产品使用合集之在同一个 Java 进程内建立三个消费对象并设置三个消费者组订阅同一主题和标签的情况下,是否会发生其中一个消费者组无法接收到消息的现象
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。
131 1
|
存储 监控 Cloud Native
剖析Linux网络包接收过程:掌握数据如何被捕获和分发的全过程(上)
剖析Linux网络包接收过程:掌握数据如何被捕获和分发的全过程
|
7月前
|
监控 安全 持续交付
【专栏】Webhook是服务器主动发送事件通知的机制,打破传统客户端轮询模式,实现数据实时高效传递。
【4月更文挑战第29天】Webhook是服务器主动发送事件通知的机制,打破传统客户端轮询模式,实现数据实时高效传递。常用于持续集成部署、第三方服务集成、实时数据同步和监控告警。具有实时性、高效性和灵活性优势,但也面临安全风险和调试挑战。理解并善用Webhook能提升系统性能,广泛应用于现代软件开发和集成。
458 0
|
存储 网络协议 Linux
剖析Linux网络包接收过程:掌握数据如何被捕获和分发的全过程(下)
剖析Linux网络包接收过程:掌握数据如何被捕获和分发的全过程
|
存储 消息中间件 Linux
多类型业务消息专题-顺序消息 | 学习笔记
快速学习多类型业务消息专题-顺序消息
多类型业务消息专题-顺序消息 | 学习笔记
|
NoSQL Redis 开发者
数据类型-案例:微信接收消息顺序控制| 学习笔记
快速学习数据类型-案例:微信接收消息顺序控制
数据类型-案例:微信接收消息顺序控制| 学习笔记
|
Android开发
移动应用程序设计基础——点菜单列表实现2.0(实现短信接受以及服务与广播的使用)
1.接收短信、开启和停止服务的界面布局。 2.实现短信接收服务; 3.接收广播信息。 具体功能为: 1、 实现短信金额通知功能。在订餐程序,取消自动登录功能,并在注册界面增加开启和停止短信通知服务按钮,开启短信按钮点击提交后显示短信服务启动成功对话框,短信通知服务开启后,用户点菜后,将“您点的菜品总价值***元”发到注册手机上。停止短信按钮点击提交后现实短信服务停止对话框; 2、 实现用户点餐广播功能。用户点菜后,所点菜品及用户名以广播方式通知,广播接收到后,打开一个新的页面,以列表的方式追加一条记录,显示
181 0
移动应用程序设计基础——点菜单列表实现2.0(实现短信接受以及服务与广播的使用)
|
iOS开发
iOS网络编程之五——请求回执类NSURLResponse属性简介
iOS网络编程之五——请求回执类NSURLResponse属性简介
283 0
艾伟:WCF从理论到实践(8):事件广播
本系列文章导航 WCF从理论到实践(1):揭开神秘面纱 WCF从理论到实践(2):决战紫禁之巅 WCF从理论到实践(3):八号当铺之黑色契约 WCF从理论到实践(4):路在何方 WCF从理论到实践(5):Binding细解 WCF从理论到实践(6):WCF架构 WCF从理论到实践(7):消息交换模式...
1238 0