【Android】揭秘如何利用Service与线程制造安卓后台通知栏推送

知栏推送很烦,主要是可以通过利用安卓的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字符文件,主要是两个按钮的字体,不是关键:

[html]  view plain  copy
 print ?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.   
  4.     <string name="app_name">利用Service与线程实现推送</string>  
  5.     <string name="action_settings">Settings</string>  
  6.     <string name="button1">开始服务</string>  
  7.     <string name="button2">终止服务</string>  
  8.   
  9. </resources>  

2、之后在res\layout\activity_main.xml把这两个按钮摆好,自上而下的垂直线性布局,摆横向两个匹配父布局,纵向包裹内容的按钮赋予相应的id。

[html]  view plain  copy
 print ?
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent"  
  4.     android:orientation="vertical" >  
  5.   
  6.     <Button  
  7.         android:id="@+id/button1"  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="wrap_content"  
  10.         android:text="@string/button1" />  
  11.   
  12.     <Button  
  13.         android:id="@+id/button2"  
  14.         android:layout_width="match_parent"  
  15.         android:layout_height="wrap_content"  
  16.         android:text="@string/button2" />  
  17.   
  18. </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的代码修改如下,也就是加上一行:

[html]  view plain  copy
 print ?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     package="com.servicedemo"  
  4.     android:versionCode="1"  
  5.     android:versionName="1.0" >  
  6.   
  7.     <uses-sdk  
  8.         android:minSdkVersion="8"  
  9.         android:targetSdkVersion="18" />  
  10.   
  11.     <application  
  12.         android:allowBackup="true"  
  13.         android:icon="@drawable/ic_launcher"  
  14.         android:label="@string/app_name"  
  15.         android:theme="@style/AppTheme" >  
  16.         <activity  
  17.             android:name="com.servicedemo.MainActivity"  
  18.             android:label="@string/app_name" >  
  19.             <intent-filter>  
  20.                 <action android:name="android.intent.action.MAIN" />  
  21.   
  22.                 <category android:name="android.intent.category.LAUNCHER" />  
  23.             </intent-filter>  
  24.         </activity>  
  25.         <service android:name="com.servicedemo.TimeService"></service><!-- 注册TimeService这个Service -->  
  26.     </application>  
  27.   
  28. </manifest>  
4、接着,我们要实现TimeService中的onStartCommand()方法与onDestroy()方法。

这里用到了《【Android】进度条与线程之间的消息处理》(点击打开链接)的知识。

先令线程的运行标识flag——isRun为真。开两条线程。

一条不停利用Toast显示当前时间,这里,由于Toast是处于线程的消息处理器当中,所以即使在服务中,也可以直接用到getApplicationContext()获取整个安卓设备的上下文。无须搞一个Looper辅助,此线程每隔2秒运行一次直到isRun为false终止,对Java线程不熟悉的朋友,可以参考《【Java】定时器、线程与匿名内部类》(点击打开链接)与《【Java】线程并发、互斥与同步》(点击打开链接),这里不能用定时器去搞,不然Toast会出错。

另一条线程,是利用到《【Android】利用Notification操作设备的通知栏》(点击打开链接)的知识,每隔10秒向通知栏推送消息。

在onStartCommand()中定义线程并开始,在onDestroy()只要把类全局变量isRun搞为false,让这两条线程自然死亡就可以了。

最终TimeService.java的代码如下:

[java]  view plain  copy
 print ?
  1. package com.servicedemo;  
  2.   
  3. import android.app.Notification;  
  4. import android.app.NotificationManager;  
  5. import android.app.PendingIntent;  
  6. import android.app.Service;  
  7. import android.content.Intent;  
  8. import android.os.Handler;  
  9. import android.os.IBinder;  
  10. import android.os.Message;  
  11. import android.text.format.Time;  
  12. import android.widget.Toast;  
  13.   
  14. public class TimeService extends Service {  
  15.     private boolean isRun;// 线程是否继续的标志  
  16.     private Handler handler1; // 显示当前时间线程消息处理器。  
  17.     private Handler handler2;// 推送通知栏消息的线程消息处理器。  
  18.     private int notificationCounter;// 一个用于计算通知多少的计数器。  
  19.   
  20.     @Override  
  21.     public IBinder onBind(Intent arg0) {  
  22.         // TODO Auto-generated method stub  
  23.         return null;  
  24.     }  
  25.   
  26.     @Override  
  27.     public void onDestroy() {  
  28.         super.onDestroy();  
  29.         isRun = false;  
  30.     }  
  31.   
  32.     @Override  
  33.     public int onStartCommand(Intent intent, int flags, int startId) {  
  34.         isRun = true;  
  35.         final NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);// 注册通知管理器  
  36.         new Thread(new Runnable() {  
  37.             @Override  
  38.             // 在Runnable中,如果要让线程自己一直跑下去,必须自己定义while结构  
  39.             // 如果这个run()方法读完了,则整个线程自然死亡  
  40.             public void run() {  
  41.                 // 定义一个线程中止标志  
  42.                 while (isRun) {  
  43.                     try {  
  44.                         Thread.sleep(2000);// Java中线程的休眠,必须在try-catch结构中,每2s秒运行一次的意思  
  45.                     } catch (InterruptedException e) {  
  46.                         e.printStackTrace();  
  47.                     }  
  48.                     if (!isRun) {  
  49.                         break;  
  50.                     }  
  51.                     Message msg = new Message(); // 在安卓中,不要在线程中直接现实方法,这样app容易崩溃,有什么要搞,扔到消息处理器中实现。  
  52.                     handler1.sendMessage(msg);  
  53.                 }  
  54.             }  
  55.         }).start();// 默认线程不启动,必须自己start()  
  56.         // 不停在接受线程的消息,根据消息的参数,进行处理 ,这里没有传递过来的参数  
  57.         handler1 = new Handler(new Handler.Callback() {// 这样写,就不弹出什么泄漏的警告了  
  58.                     @Override  
  59.                     public boolean handleMessage(Message msg) {  
  60.                         // 安卓显示当前时间的方法  
  61.                         Time time = new Time();  
  62.                         time.setToNow();  
  63.                         String currentTime = time.format("%Y-%m-%d %H:%M:%S");  
  64.                         Toast.makeText(getApplicationContext(),  
  65.                                 "当前时间为:" + currentTime, Toast.LENGTH_SHORT)  
  66.                                 .show();  
  67.                         return false;  
  68.                     }  
  69.                 });  
  70.         new Thread(new Runnable() {  
  71.             @Override  
  72.             // 在Runnable中,如果要让线程自己一直跑下去,必须自己定义while结构  
  73.             // 如果这个run()方法读完了,则整个线程自然死亡  
  74.             public void run() {  
  75.                 // 定义一个线程中止标志  
  76.                 while (isRun) {  
  77.                     try {  
  78.                         Thread.sleep(10000);// Java中线程的休眠,必须在try-catch结构中  
  79.                     } catch (InterruptedException e) {  
  80.                         e.printStackTrace();  
  81.                     }  
  82.                     if (!isRun) {  
  83.                         break;  
  84.                     }  
  85.                     Message msg = new Message();  
  86.                     handler2.sendMessage(msg);  
  87.                 }  
  88.             }  
  89.         }).start();// 默认线程不启动,必须自己start()  
  90.         handler2 = new Handler(new Handler.Callback() {// 这样写,就不弹出什么泄漏的警告了  
  91.                     @SuppressWarnings("deprecation")  
  92.                     @Override  
  93.                     // 这里notification.setLatestEventInfo,  
  94.                     // 设置通知标题与内容会被eclipse标志过时,  
  95.                     // 但新的方法,使用builder去设置通知的方法只能应用于android3.0以上的设备,对于android2.2的设备是无法使用的。  
  96.                     // 在现时国内有部分设备还是在android2.2的情况下,还是用这条几乎兼容所有版本安卓的“过时”方法吧!  
  97.                     public boolean handleMessage(Message msg) {  
  98.                         notificationCounter++;// 计数器+1  
  99.                         Notification notification = new Notification();  
  100.                         notification.icon = R.drawable.ic_launcher;// 设置通知图标为app的图标  
  101.                         notification.flags = Notification.FLAG_AUTO_CANCEL;// 点击通知打开引用程序之后通知自动消失  
  102.                         notification.tickerText = "显示通知";// 在用户没有拉开标题栏之前,在标题栏中显示的文字  
  103.                         notification.when = System.currentTimeMillis();// 设置发送时间  
  104.                         notification.defaults = Notification.DEFAULT_ALL;// 设置使用默认声音、震动、闪光灯  
  105.                         // 以下三行:在安卓设备任意环境中中,如果点击信息则打开MainActivity  
  106.                         Intent intent = new Intent(getApplicationContext(),  
  107.                                 MainActivity.class);  
  108.                         PendingIntent pendingIntent = PendingIntent  
  109.                                 .getActivity(getApplicationContext(), 0,  
  110.                                         intent, 0);  
  111.                         notification.setLatestEventInfo(  
  112.                                 getApplicationContext(), "消息标题""消息内容,第"  
  113.                                         + notificationCounter + "条通知",  
  114.                                 pendingIntent);  
  115.                         notificationManager.notify(notificationCounter,  
  116.                                 notification);// 要求通知管理器发送这条通知,其中第一个参数是通知在系统的id  
  117.                         return false;  
  118.                     }  
  119.                 });  
  120.         return START_STICKY;// 这个返回值其实并没有什么卵用,除此以外还有START_NOT_STICKY与START_REDELIVER_INTENT  
  121.     }  
  122.   
  123. }  

5、之后的MainActivity非常简单,两个按钮的点击事件,一个开始服务,另一个结束服务,为了防止启动多条服务,点击完一个按钮禁用,开启另一个按钮。

[java]  view plain  copy
 print ?
  1. package com.servicedemo;  
  2.   
  3. import android.os.Bundle;  
  4. import android.view.View;  
  5. import android.view.View.OnClickListener;  
  6. import android.widget.Button;  
  7. import android.app.Activity;  
  8. import android.content.Intent;  
  9.   
  10. public class MainActivity extends Activity {  
  11. private Button button1;  
  12. private Button button2;  
  13.     @Override  
  14.     protected void onCreate(Bundle savedInstanceState) {  
  15.         super.onCreate(savedInstanceState);  
  16.         setContentView(R.layout.activity_main);  
  17.         //注册组件  
  18.         button1=(Button)findViewById(R.id.button1);  
  19.         button2=(Button)findViewById(R.id.button2);  
  20.         //程序开始开启Service,禁用开始按钮  
  21.         startService(new Intent(MainActivity.this,TimeService.class));  
  22.         button1.setEnabled(false);  
  23.         //两个按钮的点击事件  
  24.         button1.setOnClickListener(new OnClickListener() {            
  25.             @Override  
  26.             public void onClick(View arg0) {  
  27.                 startService(new Intent(MainActivity.this,TimeService.class));  
  28.                 button1.setEnabled(false);  
  29.                 button2.setEnabled(true);  
  30.             }  
  31.         });  
  32.         button2.setOnClickListener(new OnClickListener() {            
  33.             @Override  
  34.             public void onClick(View arg0) {  
  35.                 stopService(new Intent(MainActivity.this,TimeService.class));  
  36.                 button1.setEnabled(true);  
  37.                 button2.setEnabled(false);  
  38.             }  
  39.         });  
  40.           
  41.     }  
  42.   

  1. }  




Android:退出程序后保持Serivce开启不关闭  




Android中,service的开启,默认是绑定activity的,是activity级的。
如果要实现当退出程序后,保持Service的运行,那么需要把service设置成为system级的,设置方法:
在AndroidManifest.xml中注册service时,加上属性android:process,如:

<service
      android:name = "com.jansun.pushnotification.PushNotificationService"
      android:enabled = "true"
      android:process = "system"
      />

     
另外,还要在启动service时,加入FLAG_ACTIVITY_NEW_TASK标签,如:

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);
}


至此,当你退出程序后,service还在系统后台正常运行,目标达成。






Android:Service开机随机启动

步骤:
1. 写一个BootBroadcastReceiver类,继承BroadcastReceiver:

public class BootBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive ( Context context , Intent intent ) {
        Intent i = new Intent ( ctx , PushNotificationService . class );
i . setAction ( ACTION_STOP );
ctx . startService ( i );
    }
}


2. 在AndroidManifest.xml上注册  BootBroadcastReceiver:

<receiver  
            android:name = "com.jansun.broadcast.BootBroadcastReceiver"
            >
            <intent-filter>
                <action android:name = "android.intent.action.BOOT_COMPLETED" />
                
            </intent-filter>
        </receiver>


(注:你的service要注册为system级的Service,详细可以参考我的另一篇博文 Android:退出程序后保持Serivce开启不关闭

3. 别忘了在AndroidManifest.xml中加上权限:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>


原理,Android在系统启动完成后,会广播一个 BOOT_COMPLETED的广播,那么我们想开机启动Service,当然就要知道什么时候开机完成,那么我们就要写一个BroadcastReceiver类来监听Android系统启动完成的动作,从而方便我们在开机完成后做动作。
在BroadcastReceiver类中,监听到 android.intent.action.BOOT_COMPLETED后,我们就在onReceive方法中,启动service.
这样,我们就完成了service的开机启动了。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值