您的位置:首页 > 移动开发 > Android开发

Android 杀不掉的后台服务的一种实现

2013-09-02 00:25 666 查看
有些应用程序需要后台的常驻服务,但没有任何处理时服务会被系统不定时的杀掉,特别是在内存不足时。同时服务也极容易被第三方软件释放内存时清理掉,今天同过一种实现方式来解决上述问题。

在上代码前,我们应该简单知道Android对于进程的一些管理方式,下面就看下进程的生命:

Android系统会尽量维持一个进程的生命,直到最终需要为新的更重要的进程腾出内存空间。为了决定哪个该杀哪个该留,系统会跟据运行于进程内的组件的和组件的状态把进程置于不同的重要性等级。当需要系统资源时,重要性等级越低的先被淘汰。

重要性等级被分为5个档。下面列出了不同类型的进程的重要性等级(第一个进程类型是最重要的,也是最后才会被杀的):

1前台进程

用户当前正在做的事情需要这个进程。如果满足下面的条件,一个进程就被认为是前台进程:
这个进程拥有一个正在与用户交互的Activity(这个Activity的onResume()方法被调用)。
这个进程拥有一个绑定到正在与用户交互的activity上的Service。
这个进程拥有一个前台运行的Service — service调用了方法startForeground().
这个进程拥有一个正在执行其任何一个生命周期回调方法(onCreate(),onStart(),或onDestroy())的Service。
这个进程拥有正在执行其onReceive()方法的BroadcastReceiver。

通常,在任何时间点,只有很少的前台进程存在。它们只有在达到无法调合的矛盾时才会被杀--如内存太小而不能继续运行时。通常,到了这时,设备就达到了一个内存分页调度状态,所以需要杀一些前台进程来保证用户界面的反应

2可见进程

一个进程不拥有运行于前台的组件,但是依然能影响用户所见。满足下列条件时,进程即为可见:
这个进程拥有一个不在前台但仍可见的Activity(它的onPause()方法被调用)。当一个前台activity启动一个对话框时,就出了这种情况。

3服务进程

一个可见进程被认为是极其重要的。并且,除非只有杀掉它才可以保证所有前台进程的运行,否则是不能动它的。
这个进程拥有一个绑定到可见activity的Service。
一个进程不在上述两种之内,但它运行着一个被startService()所启动的service。
尽管一个服务进程不直接影响用户所见,但是它们通常做一些用户关心的事情(比如播放音乐或下载数据),所以系统不到前台进程和可见进程活不下去时不会杀它。

4后台进程

一个进程拥有一个当前不可见的activity(activity的onStop()方法被调用)。
这样的进程们不会直接影响到用户体验,所以系统可以在任意时刻杀了它们从而为前台、可见、以及服务进程们提供存储空间。通常有很多后台进程在运行。它们被保存在一个LRU(最近最少使用)列表中来确保拥有最近刚被看到的activity的进程最后被杀。如果一个activity正确的实现了它的生命周期方法,并保存了它的当前状态,那么杀死它的进程将不会对用户的可视化体验造成影响。因为当用户返回到这个activity时,这个activity会恢复它所有的可见状态。

5空进程

一个进程不拥有入何active组件。
保留这类进程的唯一理由是高速缓存,这样可以提高下一次一个组件要运行它时的启动速度。系统经常为了平衡在进程高速缓存和底层的内核高速缓存之间的整体系统资源而杀死它们。

注意,这五种进程都不会保证不被杀死。

既然这所有进程都不能保证被系统杀死,那么唯一的办法就是在杀死后立即重启,这样就可以保证后台服务一直在运行。一种简单的办法就是通过向添加一个AlarmManager来周期性的启动服务。下面就通过代码说明该方法。

首先,在Activity中定义两个按钮,一个为开启后台服务,一个为停止后台服务

package com.jacky.permanent;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity
{
private Button startButton = null;
private Button stopButton = null;
private Intent intent = null;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//get instance
startButton = (Button) findViewById(R.id.startButton);
stopButton = (Button) findViewById(R.id.stopButton);
intent = new Intent(MainActivity.this, MyService.class);

//set start service button click listener
startButton.setOnClickListener(new OnClickListener()
{

@Override
public void onClick(View view)
{
startService(intent);
}
});

//set stop service button click listener
stopButton.setOnClickListener(new OnClickListener()
{

@Override
public void onClick(View view)
{
stopService(intent);
}
});
}
}


接下来在自定义服务中向系统添加一个Alarm,每个一分钟启动一次服务。

package com.jacky.permanent;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;

public class MyService extends Service
{
AlarmManager mAlarmManager = null;
PendingIntent mPendingIntent = null;

@Override
public void onCreate()
{
//start the service through alarm repeatly
Intent intent = new Intent(getApplicationContext(), MyService.class);
mAlarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
mPendingIntent = PendingIntent.getService(this, 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK);
long now = System.currentTimeMillis();
mAlarmManager.setInexactRepeating(AlarmManager.RTC, now, 60000, mPendingIntent);

super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
Toast.makeText(getApplicationContext(), "Callback Successed!", Toast.LENGTH_LONG).show();
return START_STICKY;
}

@Override
public IBinder onBind(Intent intent)
{
return null;
}

@Override
public void onDestroy()
{

super.onDestroy();
}
}


注意:这里面是每隔一分钟启动一次服务,而不是检测服务是否开启。大家可能已经注意到,在Service的onStartCommand()回调函数中返回值是START_STICKY,这个标志表示在服务被杀死后系统会自动将其重启,但不幸的是这个重启的时间是随机推迟的,不由我们掌控,而且问题是,若在重启失败,后台服务会始终处于随机推迟时间(这个时间会随着重启次数的增加而变长)的重启状态中。此事若用常规方法检测服务是否存在,如下面代码:

public boolean isServiceRunning(String strServiceName)
{
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);

for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE))
{
String allServiceName = service.service.getClassName();
if (allServiceName.equals(strServiceName))
{
return true;
}
}
return false;
}


就会得到返回值为true,即服务在运行,这样就无法启动服务了。

然后别忘在AndroidManifest.xml注册服务

<service
android:name="com.jacky.permanent.MyService"
android:enabled="true"
android:process=":remote" >
</service>


最后为了让程序程序锦上添花,我们可以静态注册一个BroadcastReceiver监听开机事件,可以根据需要在开机时启动服务。程序如下:

首先,AndroidManifest.xml静态注册BootBroadcastReceiver监听开机广播。

<receiver android:name="com.jacky.permanent.BootBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.HOME" />
</intent-filter>
</receiver>


然后,在BootBroadcastReceiver中开启服务

package com.jacky.permanent;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class BootBroadcastReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()))
{
Intent startServiceIntent = new Intent(context, MyService.class);
startServiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startService(startServiceIntent);
}
}
}


到此程序结束,这样就可以保证服务常驻系统,最后若每次开启服务时intent需要额外的参数,则可以修改程序使用AlarmManager定时发送自定义广播,在Service中注册一个动态广播接收器,接收到广播后可以读出通过Sharedpreference或数据库存储的信息,填充intent后再开启服务,程序比较简单,代码就不贴了。

此为常驻内存服务的一种实现方法,还有如监听ACTION_TIME_TICK系统广播等回调服务。大家若有更好的方法保证后台服务不被系统或第三方软件杀掉,欢迎提出一起交流!

最后,附上Demo的下载地址:http://download.csdn.net/detail/u010538765/6195541
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: