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

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

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


目录
相关文章
|
3月前
|
设计模式 小程序 API
小程序之页面通信&派发通知
小程序之页面通信&派发通知
|
8月前
|
网络协议 网络架构
数据从发出到接收的细节介绍{封装与解封装}
本文将介绍了详细的封装在每一层的具体的操作,可以让大家学习到数据从发出到收到的具体过程。
|
5月前
|
存储 监控 Cloud Native
剖析Linux网络包接收过程:掌握数据如何被捕获和分发的全过程(上)
剖析Linux网络包接收过程:掌握数据如何被捕获和分发的全过程
|
5月前
|
存储 网络协议 Linux
剖析Linux网络包接收过程:掌握数据如何被捕获和分发的全过程(下)
剖析Linux网络包接收过程:掌握数据如何被捕获和分发的全过程
|
7月前
[虚幻引擎插件介绍] DTGlobalEvent 蓝图全局事件, Actor, UMG 相互回调,自由回调通知事件函数,支持自定义参数。
本插件可以在虚幻的蓝图 Actor, Obiect,UMG 里面指定绑定和执行消息,可带自定义参数。 参数支持 Bool,Byte,Int,Int64,Float,Name,String,Text,Vector,Rotator,Transform,Object,Actor。
42 0
|
消息中间件 运维 监控
多类型业务消息专题-普通消息 | 学习笔记(一)
快速学习多类型业务消息专题-普通消息
139 0
 多类型业务消息专题-普通消息 | 学习笔记(一)
|
NoSQL Redis 开发者
数据类型-案例:微信接收消息顺序控制| 学习笔记
快速学习数据类型-案例:微信接收消息顺序控制
189 0
数据类型-案例:微信接收消息顺序控制| 学习笔记
|
Android开发
移动应用程序设计基础——点菜单列表实现2.0(实现短信接受以及服务与广播的使用)
1.接收短信、开启和停止服务的界面布局。 2.实现短信接收服务; 3.接收广播信息。 具体功能为: 1、 实现短信金额通知功能。在订餐程序,取消自动登录功能,并在注册界面增加开启和停止短信通知服务按钮,开启短信按钮点击提交后显示短信服务启动成功对话框,短信通知服务开启后,用户点菜后,将“您点的菜品总价值***元”发到注册手机上。停止短信按钮点击提交后现实短信服务停止对话框; 2、 实现用户点餐广播功能。用户点菜后,所点菜品及用户名以广播方式通知,广播接收到后,打开一个新的页面,以列表的方式追加一条记录,显示
136 0
移动应用程序设计基础——点菜单列表实现2.0(实现短信接受以及服务与广播的使用)
【EventBus】事件通信框架 ( 实现几个关键的封装类 | 消息中心 | 订阅注解 | 订阅方法封装 | 订阅对象-方法封装 | 线程模式 )
【EventBus】事件通信框架 ( 实现几个关键的封装类 | 消息中心 | 订阅注解 | 订阅方法封装 | 订阅对象-方法封装 | 线程模式 )
83 0
|
缓存
【EventBus】事件通信框架 ( 订阅方法注册 | 检查订阅方法缓存 | 反射获取订阅类中的订阅方法 )(一)
【EventBus】事件通信框架 ( 订阅方法注册 | 检查订阅方法缓存 | 反射获取订阅类中的订阅方法 )(一)
93 0