刚开始接到这个任务的时候,觉得很难,无从下手。接着就在网上查资料,查来查去都没有查到有用的信息。 没办法,问下MTK的工程师:
断断续续提供了一下信息:
1.系统当中的Lowmemory killer会做这件事,每隔一段时间去杀一些后台进程,提供一个链接参考原理:
http://tech.it168.com/a2011/0805/1228/000001228471_2.shtml 可以尝试调整minfree来达到目的
2.Android原本就有这样的机制:
A)AMS(activity manager services)会根据是否处于前台、是否有Services、是否被用户所感知等因素设置每个进程的优先级,每种优先级会对应
一个memory水平值。
B)Linux kernal中的一个模块LMK(Low Memory Killer)会在剩余可用Memory达到某个水位时,会将对应优先级的进程Kill以及更低的进程杀死。
C)贵司这个需求中“后台进程”这种概念太模糊,有的进程即使没有Activity处于后台显示出来也可能是重要的进程(比如:Music、FM等)。
为了不影响现有的机制,还是不改为好。
以下思想可以作为参考:
A)可以在ProcessRecord中的设置一个字段保存这个时间值。
B)之后在ActivityManagerService.computeOomAdjLocked()函数中会根据每个Process的状态设置其adj值。
C)可以在这个函数或者updateOomAdjLocked(app,hiddenAdk,TOP_APP)中根据判断新的值如果和之前的不一样且处于某个adj时,就将当前系统时间
写入之前的字段中。
D)“后台进程”这个概念太模糊,可以根据需要选定adj值。
至于adj值和Process优先级的关系比较复杂,可以参考AMS代码或网上的资料。
根据以上的信息研究了下,发现很难,还是没什么头绪。算了,还是的靠自己了。接下来就自己研究吧!
首先,因为需求里提到了应用程序和进程,那么,的弄清楚在Android中应用程序和进程是什么关系,他们之间有什么区别和联系? 不清楚,网上查资料。
下面总结一下在网上和书上查到的我认为比较好的信息:
--》谁负责管理Activity?
Android的framework框架中采用C/S的方式实现由后台服务ActivityManagerService(AMS)来管理具体的Activity实例,虽然AMS命名为ActivityManagerService
,但是他其实不仅仅管理Activity,他还管理Activity外的其他三大组件,还包括Intent、pendingintent、apk进程和task等等,具体可以查看源码
frameworks/base/services/java/com/android/server/am下面的Ams源码和相关的*Record类。 网上的资料显示在2.3以后的SDK中,Ams将原来的
HistoryRecord类重命名为ActvitryRecord类,并将其原来Ams中的一些处理抽出来成为一个ActivityStack类,每一个Activity实例对应一个ActivityRecord对象,并存放在
ActivityStack中,手机一旦启动,就会启动一个Ams服务,在Ams服务中有一个ActivityStack实例专门管理手机上的ActivityRecord实例。这样一来,不具体分析源码,
仅从框架角度来说谁负责管理Activity的问题就清晰多了。
--》Task到底是什么?
根据对上面问题的解答,既然Activity是由Ams通过ActivityStack来管理的,那么,Task又是干什么的呢?
以往基于应用(application)的程序开发中,程序具有明确的边界,一个程序就是一个应用,一个应用为了实现功能可以采用开辟新线程甚至新进程来辅助,但是应用和应用之间不能复用资源和功能。而Android引入了基于组件开发的软件框架,虽然我们开发Android程序任然使用一个apk工程一个Application的开发形式,但是对与Application的开发用到了Activity、Service等四大组件,其中的每一个组件都是可以跨应用复用的。这就是Android的神奇之处。 不过,虽然组件可以跨应用被调用,但是,一个组件所在的进程必须是在组件所在的Application进程中(这句话可以好好理解一下)。由于Android强化了组件的概念,弱化了Application的概念,所以在Android程序开发中,A应用的A组件想要使用拍照或录像的功能就可以不用去针对Camera类进行开发,直接调用系统自带的摄像头应用(称其B应用)中的组件(称其B组件)就可以了。但是,这就引发了一个新的问题,A组件跑在A应用中,B组件跑在B应用中,,自然都在不同的进程中,那么,从B组件中返回的时候,如何实现正确返回到A组件呢?Task就是来负责实现这个功能的,他是从用户角度来理解应用而建立的一个抽象的概念。因为用户所能看到的组件就是Activity,所以Task可以理解为:为实现一个功能而负责管理所有用到的Activity实例的栈。 其实查看源码,在Ams内部,并不是真的有这么一个存放Activity的Task栈,Activity还是通过ActivityStack来管理的,在ActivityRecord中有一个TaskRecord对象记录了真是的Activity实例是属于哪个Task的。Task通过一个int类型的TaskId来唯一标识,该值在手机重启时将会被置零。 我来看一个Task任务最直观的体现,重启手机,长按home键,发现弹出的最近历史任务中一个任务也没有,然后,开启A应用,长按home键,会发现有一个A应用的任务,查看手机的进程,此时应该还没有B进程的;在A应用的A组件中调用B组件,此时再看手机的进程,除了A进程外,还有个B进程,但是,长按home键,能看到的应该还是只有一个A应用。其实,这个时候,B应用已经跑起来了,但是对用户来说,他其实没有开启过B应用。 所以,Task任务自始至终都是从用户的角度出发而设计的概念,保证用户的调用逻辑。
上面的例子是一个标准的栈模式,对于大部分的状况,这样的Task模型,足以应付,但是,涉及到实际的性能、开销等问题,就会变的残酷许多。比如,启动一个浏览器,在Android中是一个 比较沉重的过程,他需要做很多初始化的工作,并且,会有不小的内存开销。但用浏览器打开一些内容,又是一般应用都会有的一个需求。设想一下,如果同时有十个运行着的应用(就会对应多个Task),都需要启动浏览器,这将是一个多么残酷的场面,十个Task栈都堆积这很雷同的浏览器Activity,是多么华丽的一种浪费啊。于是,我们会不会有这样一种设想,浏览器Activity,可不可以作为一个单独的Task而存在,不管来自哪个Task的请求,浏览器的Task 都不会归并过去。这样,虽然浏览器Activity本身需要维系的状态更多了,但是整体的开销将大大减少。 其实,google也是这么做的,在Android中,每一个Activity的Task模式,都是可以由Activity提供方(通过配置文件)和Activity使用方(通过Intent中的flag信息)进行配置和选择。当然,使用方对Activity的控制力是限制在提供方允许的范畴内进行,提供方明令禁止的模式,使用方是不能够越界使用的。
在SDK中,将两者实现Task模式配置的方式写的非常清晰了(完整可配置项,一定要看SDK,下面只是其中常用的若干项)。提供方对组件的配置,是通过 配置文件(manifest)<activity>项来进行的,而调用方,而是通过 Intent对象的flag进行择选。相对于标准的Task栈的模式,配置的主要方向有两个:一则是破坏已有栈的进出规则 或 样式;二则是开辟新的Task栈完成本应在同一Task栈中完成的任务。
对于应用开发人员而言,<activity>中的launcherMode属性是需要经常打交道的。他有四种模式:“standard”、“singleTop”、“singleTask”和“singleInstance”:
——》standard模式,是默认的也是标准的Task模式,在没有其他因素影响下,使用此模式的Activity会构造一个Activity实例,加入到调用者的Task栈中去,对于使用频度一般、开销一般、什么都一般的Activity而言,standard模式无疑是最合适的,因为他逻辑简单条理清晰,所以是默认的选择。
——》singleTop模式,基本上与standard一致,仅在被请求的Activity正好位于栈顶时有所区别。此时,配置成singleTop的Activity 不再会构造新的实例加入到Task栈中,而是将新来的Intent发送到栈顶的Activity中,栈顶的Activity可以通过重载onNewIntent来处理新的Intent(当然,也可以无视)。这个模式,降低了Activity位于栈顶是的重复开销,更避免了一些奇特行为(想象一下,如果在栈顶连续几个都是同样的Activity,在一级一级退出的时候,这是怎样的用户体验........),很适合一些会有更新的列表Activity展示。一个活生生的例子就是:在Android默认提供的应用中,浏览器(Browser)的书签Activity(BrowserBookmarkPage) 用的就是singleTop。
——》singleTop模式原有栈的逻辑(复用了栈顶,而没有构造新元素进栈),但并未开辟专属的Task。而singleTask和singleInstance,则都采取的另辟Task的蹊径。标志为singleTask的Activity,最多只有一个实例存在,并且,位于以他为跟的Task中。所有对该Activity的请求,都会跳到该Activity的Task中展开进行。singleTask,很像概念中的单件模式,所有的修改都是基于一个实例,这通常用在构造成本很大,但切换成本较小的Activity中。在Android源码提供的应用中,该模式被广泛采用,最典型的例子 还是浏览器应用的主Activity(名为Browser),他展示当期tab,当期页面内容的窗口。他的构造成本大,但页面的切换还是较快的。 相比之下,singleInstance显得更为极端一些。在大部分的时候singleInstance和singleTask完全一致,唯一的不同在于:singleInstance的Activity是他所在栈中的仅有的一个Activity,如果涉及到其他Activity,都移交到其他Task中进行。这使得singleInstance的Activity像一座孤岛,彻底的黑盒,他不关注请求来自何方,也不计较后续有谁执行。在Android默认的各个应用中,很少有这样的Activity。
除了launchMode可以用来调配Task,<activity>的另一个属性taskAffinity,也是常常被使用。taskAffinity,是一种物以类聚的思想,他倾向于将taskAffinity属性相同的Activity扔进同一个Task中。不过,他的约束力相较于launchMode而言弱了许多。只有当<actiivity>中的allowTaskReparen ting设置为true或调用方将Intent的flag添加FLAG_ACTIVITY_NEW_TASK属性时才会生效。如果有机会用到Android的Notification机制就会知道,每一个由Notification进行触发的Activity都必须是一个设成FLAG_ACTIVITY_NEW_TASK的Intent来调用。这时候,开发者很可能需要妥善配置taskAffinity属性,使得调用起来的Activity能够找到组织,在同一taskAffinity的Task中进行运行。
进程,
在大多数其他平台的开发中,每个开发人员对自己应用的进程模型都有非常清晰的了解。比如,一个控制台程序,我们知道他从main函数开始启动一个进程,到main函数结束,进程执行完毕退出;在UI程序中,往往是有一个消息循环在跑,当接收到Exit消息后,退出消息循环结束进程。在该程序运行过程中,启动了什么进程,和第三方进程进行通信等操作,每个开发者都是心如明镜一本账算的清清楚楚。进程边界在这里犹如国界一般,每一次穿越都会留下深深的印记。 在android程序中,开放人员可以直接感知的往往是Task而已。倍感清晰的是组件边界,而进程边界变得难以琢磨,甚至有了进程托管一说。android中不但剥夺了手工锻造内存的权力,连手工处置进程的权责也毫不犹豫的独占了。 当然,android隐藏进程细节并不是刻意为之,而是自然而然水到渠成的事情。如果我们把传统的应用称为 面向进程的开发,那么,在android中,我们做的就是面向组件的开发。android组件间的跳转和通信,都是在三方介入的前提下进行,正由于这种介入,使得两个组件间不会直接发生联系(Service的通信,是不需要三方介入的,因此,android把他们全部假设成为穿越进程边界,统一基于RPC来通信,这样,也是为了掩盖进程细节),其中是否穿越进程细节也就变得不重要了。因此,如果这时候,还需要开发者关注进程,就会变得奇怪,所以,android将所有的进程一并托管去了,上层无需知道进程的生死和通信细节。
在android的底层,进程构造了底部的一个运行池,不仅仅是Task中的各个Activity组件,其他三大组件Service、Content Provider、Broadcast Receiver,都是寄宿在底层某个进程中进行运转。在这里,进程更像一个资源池(概念形如线程池,上层要用的时候取一个就好,而不需要关注具体去哪一个),只是为了承载各个组件的运行,而各个组件之间的逻辑关系,他们并不关心。但我们可以想象,为了保证整体性,在默认情况下,android肯定倾向于将同一Task、同一应用的组件扔进同一个进程内,当然,出于效率考虑,android也是允许开发者进行配置的。
接下来就看下我的实现: 通过上面的信息,我感觉在android中,进程这个东西很隐晦,要我去控制它我觉的我还没用达到这个高度。所以,就只能从其他地方入手,正好,以前做过“一键清除历史任务”的需求,让我想到,不必去关心进程,而是考虑Task。真是山重水复疑无路,柳暗花明又一村啊! 插个话题,在研究的过程中由于没有仔细看源码导致走了歪路,有点不记得了,但是,我要说的是,在framework的某些模块(具体我不大清楚)中添加接口函数或变量 需要update API,否则,单编模块不会报错,但是,全编的话就会报错,提示需更新api,update current.txt。这时我们需要make update-api(具体可能不同,可以在网上查下)会修改code/frameworks/base/api/current.txt文件,然后,再全编就不会报错了,记得把curren.txt文件同步到服务器上,否则,会导致其他人编译通不过,以前2.3系统是current.xml,现在4.0的是current.txt。
同过跟踪代码最终发现,在TaskRecord.java中有一个包访问权限的方法:long getInactiveDuration(),它返回Task持续的时间,诶,这不正是我们想要的吗!这下把我高兴的...........! 于是,有了思想:在后台开启一个线程,设置一个时间值,然后个一段时间就去检测一下所有后台Task持续的时间,如果超过了设定的时间范围,就结束掉这个Task。 前面我们提到AMS很重要,所以,我发现在ActivityMangerService.java中有一个成员变量mRecentTasks记录了所有最近所有历史任务,当然,系统对这个历史任务的数量是有限制的。 系统启动时,会去启动AMS(关于这部分的内容之后另起章节讲解),所以,线程函数就写在这里了,以下简略介绍一下代码实现:
在构造函数中:
if(FeatureOption.RGK_MEM_OPTIMIZATION_SUPPORT){
Message msg = mHandler.obtainMessage(RGK_MEM_OPTIMIZE_MSG);
mHander.sendMessageDelayed(msg,5*1000);
}
然后,在Handler中:
case RGK_MEM_OPTIMIZE_MSG:{
if(FeatureOption.RGK_MEM_OPTIMIZATION_SUPPORT){
final int waitTime = mContext.getResources().getInterger(com.android.internal.R.interger.wait_mem_optimization_time);
final int waitTime = mContext.getResources().getInterger(com.android.internal.R.interger.kill_mem_optimization_time);
new Thread(){
public void run(){
while(true){
try{
Thread.sleep(waitTime*1000);
int index = 0;
final int M = mRecentTask.size();
while(index<M){
TaskRecord t = mRecentTadk.get(index);
++index;
if(t.getInactiveDuration()>=(killTime*60*1000)&&null != t.realActivity){
Intent intent = new Intent();
intent.setAction(com.android.removeTask);
intent.putExtra("taskId",t.taskId);
intent.putExtra("realActivity",t.realActivity.getPackageName());
mContext.sendBroadcast(intent);
}
}
}catch(Exception e){
}
}
}
}.start();
}
}
从上面的代码我们可以看到,发送了广播,这个广播在哪里接收呢? 在SystemUI模块,因为,在这里写结束Task的代码很方便。 因此,写了一个Service:
alps/frameworks/base/packages/systemUI/src/com/android/systemui/MemOptimizeBgkService.java 不要忘记了注册Service,alps/frameworks/base/packages/systemui/AndroidManifest.xml
<service android:name=".MemOptimizeBgkService"
android:exported="true" />
因为,我们需要结束Task,所以还需要添加一些权限:
<uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
我们写的这个Service是什么时候起来的呢? SystemUI写了一个BroadcastReceiver叫BootReceiver.java,顾名思义,它会接收到开机完成的广播,然后,去处理一些事情。可在AndroidManifest.xml中看到:
<receiver android:name=".BootReceiver" androidprv:primaryUserOnly="true">
<intent-filter>
<action android:name="android:intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
启动Service的代码:
if(FeatureOption.RGK_MEM_OPTIMIZATION_SUPPORT){
Intent MemOptBgk = new Intent(context,com.android.systemui.MemOptimizeBgkService.class);
context.startService(MemOptBgk);
}
接下来,我们看看MemOptimizeBgkService.java的实现:
public class MemOptimizeBgkService extends Service{
private IntentFilter mIntentFilter;
Context mContext;
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver(){
public void onReceive(Context context,Intent intent){
if("com.android.removeTask".equals(intent.getAction())){
int taskId = intent.getIntExtra("taskId",-1);
String realActivity = intent.getStringExtra("realActivity");
if(null != realActivity){
clearRamUseage(realActivity,taskId);
}
}
}
};
public void onCreate(){
super.onCreate();
mContext = this;
mIntentFilter = new IntentFilter();
mIntentFilter.addAction("com.android.removeTask");
registerReceiver(mIntentReceiver,mIntentFilter);
}
public void onDestroy(){
super.onDestroy();
unregisterReceiver(mIntentReceiver);
}
public IBinder onBind(Intent intent){
return null;
}
private void clearRamUseage(String packageName,int taskId){
final PackageManager pm = getPackageManager();
ActivityManager activityManager = (ActivityManager)this.getSystemService(ACTIVITY_SERVICE);
fianl ActivityManager am= (ActivityManager)getSystemService(ACTIVITY_SERVICE);
final List<ActivityManager.RecentTaskInfo> recentTasks=am.getRecentTasks(23,ActivityManager.RECENT_IGNORE_UNAVAILABLE);
Activity homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME).ersolveActivityInfo(pm,0);
int numTasks = recentTasks.size();
int index = 0;
for(index=1;index < numTasks;++index){//Don`t remove the current task(0)
final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(index);
Intent intent = new Intent(recentInfo.baseIntent);
if(recentInfo.origActivity != null){
intent.setComponent(recentInfo.origActivity);
}
//Don`t load the current home activity
if(isCurrentHomeActivity(intent.getComponent(),homeInfo))
continue;
//Don`t load ourselves
if(intent.getComponent().getPackageName().equals(getPackageName()))
continue;
if(intent.getComponent().getPackageName().equals(packageName)){
activityManager.killBackgroundProcesses(intent.getComponent().getPackageName());
am.removeTask(taskId,ActivityManager.REMOVE_TASK_KILL_PROCESS);
am.forceStopPackage(intent.getComponent().getPackageName());
}
}
}
private boolean isCurrentHomeActivity(ComponentName component,ActivityInfo homeInfo){
if(homeInfo == null){
final PackageManager pm = getPackageManager();
homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(pm,0);
}
return homeInfo !=null && homeInfo.packageName.equals(component.getPackageName()) && homeInfo.name.equals(component.getClassName());
}
}
以上部分代码的实现参考了RecentTasksLoader.java里面的代码。
总结下:本人是第一次写博客,也是第一次系统的写需求实现,亦是第一次android设计,有很多地方写的不好,忘各位多多指正,不慎感激。 写东西真的很有用,很多东西在当时写代码的时候是想不到的,比如,上面的service就是多余的,直接写一个BroadcastReceiver就可以搞定。