知栏推送很烦,主要是可以通过利用安卓的Service功能,在app被点击Home键或者返回键之后,依旧可以在挂载后台运行。反正Home键或者返回键只能杀死Activity而不能终结Service的,不像Windows点关闭键默认是关闭程序界面+程序线程。
本文的意义相当于在Windows中制造一条线程,或者是对Windows应用程序的关闭键进行重写,让其应用程序的关闭仅仅是隐藏界面,不杀死进程。
理论上,只要这个Service不被安卓系统因内存不足所回收,会一直持续到设备重启,或者,app良心点,提供一个关闭Service的功能。
Service一般配合Android的线程处理机制所实现,下面举一个小例子说明这个问题:
如图,此app一启动,则开启两条进程,一条是每2秒系统搞一个Toast显示当前时间,一条是每10秒在通知栏推送一条消息。
这app真是够流氓的,你完全可以把显示时间改改,改成一段Java从远程获取数据的代码,详情看见《【Java】读取网页中的内容》(点击打开链接),来下些垃圾应用,用文件流放到手机内存,等待这条线程结束再用通知栏推送给用户,真的是呵呵了。据说,用户的流量就这样跑光的。
但是,本app同样提供一个停止服务的按钮,终止这两条线程。制作过程如下:
1、res\values\strings.xml字符文件,主要是两个按钮的字体,不是关键:
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <string name="app_name">利用Service与线程实现推送</string>
- <string name="action_settings">Settings</string>
- <string name="button1">开始服务</string>
- <string name="button2">终止服务</string>
- </resources>
2、之后在res\layout\activity_main.xml把这两个按钮摆好,自上而下的垂直线性布局,摆横向两个匹配父布局,纵向包裹内容的按钮赋予相应的id。
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" >
- <Button
- android:id="@+id/button1"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/button1" />
- <Button
- android:id="@+id/button2"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/button2" />
- </LinearLayout>
3、之后,正式开始我们推送线程的制作,在src文件夹工程包下这里是com.ServiceDemo新建一个继承android.app.Service类的TimeService.java,如下图:
之后,在空白位置按Alt+Shift+S之后再按V,表示重写Service下的onStartCommand()方法与onDestroy()方法,这两个方法分别可以被任何App中的,利用Intent指定为本服务TimeService.java的startService()方法与stopService()所触发。
这也就是所谓的“安卓Service生命周期”:context.startService()->onCreate()->onStart()->Service running->context.stopService()->onDestroy()->Service stop。其中:
如果Service还没有运行,则android先调用onCreate()然后调用onStart();
如果Service已经运行,则只调用onStart(),所以一个Service的onStart方法可能会重复调用多次。
从父方法拿到onStartCommand()方法与onDestroy()方法之后先别急着实现,与《【Android】多个Activity之间利用bundle传递数值》(点击打开链接),新建的界面Activity之后,要先去AndroidManifest.xml注册一样,这里新建服务Service之后,同样要去AndroidManifest.xml注册这个TimeService。AndroidManifest.xml的代码修改如下,也就是加上一行:
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.servicedemo"
- android:versionCode="1"
- android:versionName="1.0" >
- <uses-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="18" />
- <application
- android:allowBackup="true"
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <activity
- android:name="com.servicedemo.MainActivity"
- android:label="@string/app_name" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <service android:name="com.servicedemo.TimeService"></service><!-- 注册TimeService这个Service -->
- </application>
- </manifest>
这里用到了《【Android】进度条与线程之间的消息处理》(点击打开链接)的知识。
先令线程的运行标识flag——isRun为真。开两条线程。
一条不停利用Toast显示当前时间,这里,由于Toast是处于线程的消息处理器当中,所以即使在服务中,也可以直接用到getApplicationContext()获取整个安卓设备的上下文。无须搞一个Looper辅助,此线程每隔2秒运行一次直到isRun为false终止,对Java线程不熟悉的朋友,可以参考《【Java】定时器、线程与匿名内部类》(点击打开链接)与《【Java】线程并发、互斥与同步》(点击打开链接),这里不能用定时器去搞,不然Toast会出错。
另一条线程,是利用到《【Android】利用Notification操作设备的通知栏》(点击打开链接)的知识,每隔10秒向通知栏推送消息。
在onStartCommand()中定义线程并开始,在onDestroy()只要把类全局变量isRun搞为false,让这两条线程自然死亡就可以了。
最终TimeService.java的代码如下:
- package com.servicedemo;
- import android.app.Notification;
- import android.app.NotificationManager;
- import android.app.PendingIntent;
- import android.app.Service;
- import android.content.Intent;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Message;
- import android.text.format.Time;
- import android.widget.Toast;
- public class TimeService extends Service {
- private boolean isRun;// 线程是否继续的标志
- private Handler handler1; // 显示当前时间线程消息处理器。
- private Handler handler2;// 推送通知栏消息的线程消息处理器。
- private int notificationCounter;// 一个用于计算通知多少的计数器。
- @Override
- public IBinder onBind(Intent arg0) {
- // TODO Auto-generated method stub
- return null;
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- isRun = false;
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- isRun = true;
- final NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);// 注册通知管理器
- new Thread(new Runnable() {
- @Override
- // 在Runnable中,如果要让线程自己一直跑下去,必须自己定义while结构
- // 如果这个run()方法读完了,则整个线程自然死亡
- public void run() {
- // 定义一个线程中止标志
- while (isRun) {
- try {
- Thread.sleep(2000);// Java中线程的休眠,必须在try-catch结构中,每2s秒运行一次的意思
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (!isRun) {
- break;
- }
- Message msg = new Message(); // 在安卓中,不要在线程中直接现实方法,这样app容易崩溃,有什么要搞,扔到消息处理器中实现。
- handler1.sendMessage(msg);
- }
- }
- }).start();// 默认线程不启动,必须自己start()
- // 不停在接受线程的消息,根据消息的参数,进行处理 ,这里没有传递过来的参数
- handler1 = new Handler(new Handler.Callback() {// 这样写,就不弹出什么泄漏的警告了
- @Override
- public boolean handleMessage(Message msg) {
- // 安卓显示当前时间的方法
- Time time = new Time();
- time.setToNow();
- String currentTime = time.format("%Y-%m-%d %H:%M:%S");
- Toast.makeText(getApplicationContext(),
- "当前时间为:" + currentTime, Toast.LENGTH_SHORT)
- .show();
- return false;
- }
- });
- new Thread(new Runnable() {
- @Override
- // 在Runnable中,如果要让线程自己一直跑下去,必须自己定义while结构
- // 如果这个run()方法读完了,则整个线程自然死亡
- public void run() {
- // 定义一个线程中止标志
- while (isRun) {
- try {
- Thread.sleep(10000);// Java中线程的休眠,必须在try-catch结构中
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (!isRun) {
- break;
- }
- Message msg = new Message();
- handler2.sendMessage(msg);
- }
- }
- }).start();// 默认线程不启动,必须自己start()
- handler2 = new Handler(new Handler.Callback() {// 这样写,就不弹出什么泄漏的警告了
- @SuppressWarnings("deprecation")
- @Override
- // 这里notification.setLatestEventInfo,
- // 设置通知标题与内容会被eclipse标志过时,
- // 但新的方法,使用builder去设置通知的方法只能应用于android3.0以上的设备,对于android2.2的设备是无法使用的。
- // 在现时国内有部分设备还是在android2.2的情况下,还是用这条几乎兼容所有版本安卓的“过时”方法吧!
- public boolean handleMessage(Message msg) {
- notificationCounter++;// 计数器+1
- Notification notification = new Notification();
- notification.icon = R.drawable.ic_launcher;// 设置通知图标为app的图标
- notification.flags = Notification.FLAG_AUTO_CANCEL;// 点击通知打开引用程序之后通知自动消失
- notification.tickerText = "显示通知";// 在用户没有拉开标题栏之前,在标题栏中显示的文字
- notification.when = System.currentTimeMillis();// 设置发送时间
- notification.defaults = Notification.DEFAULT_ALL;// 设置使用默认声音、震动、闪光灯
- // 以下三行:在安卓设备任意环境中中,如果点击信息则打开MainActivity
- Intent intent = new Intent(getApplicationContext(),
- MainActivity.class);
- PendingIntent pendingIntent = PendingIntent
- .getActivity(getApplicationContext(), 0,
- intent, 0);
- notification.setLatestEventInfo(
- getApplicationContext(), "消息标题", "消息内容,第"
- + notificationCounter + "条通知",
- pendingIntent);
- notificationManager.notify(notificationCounter,
- notification);// 要求通知管理器发送这条通知,其中第一个参数是通知在系统的id
- return false;
- }
- });
- return START_STICKY;// 这个返回值其实并没有什么卵用,除此以外还有START_NOT_STICKY与START_REDELIVER_INTENT
- }
- }
5、之后的MainActivity非常简单,两个按钮的点击事件,一个开始服务,另一个结束服务,为了防止启动多条服务,点击完一个按钮禁用,开启另一个按钮。
- package com.servicedemo;
- import android.os.Bundle;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.app.Activity;
- import android.content.Intent;
- public class MainActivity extends Activity {
- private Button button1;
- private Button button2;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- //注册组件
- button1=(Button)findViewById(R.id.button1);
- button2=(Button)findViewById(R.id.button2);
- //程序开始开启Service,禁用开始按钮
- startService(new Intent(MainActivity.this,TimeService.class));
- button1.setEnabled(false);
- //两个按钮的点击事件
- button1.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- startService(new Intent(MainActivity.this,TimeService.class));
- button1.setEnabled(false);
- button2.setEnabled(true);
- }
- });
- button2.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- stopService(new Intent(MainActivity.this,TimeService.class));
- button1.setEnabled(true);
- button2.setEnabled(false);
- }
- });
- }
- }
Android:退出程序后保持Serivce开启不关闭
<serviceandroid:name = "com.jansun.pushnotification.PushNotificationService"android:enabled = "true"android:process = "system"/>
public static void actionStart ( Context ctx ) {//System.out.println("---- Notification service started!");Intent i = new Intent(ctx, PushNotificationService .class);i.setAction(ACTION_START);i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);ctx.startService(i);}
Android:Service开机随机启动
步骤:public class BootBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive ( Context context , Intent intent ) {Intent i = new Intent ( ctx , PushNotificationService . class );i . setAction ( ACTION_STOP );ctx . startService ( i );}}
<receiverandroid:name = "com.jansun.broadcast.BootBroadcastReceiver"><intent-filter><action android:name = "android.intent.action.BOOT_COMPLETED" /></intent-filter></receiver>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>