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

在这里插入图片描述

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫头虎

一分也是爱,打赏博主成就未来!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值