曾经做的一个需求:如果一个应用在后台运行超过三小时,系统自动杀掉这个应用 以释放内存 来达到内存优化的目的

刚开始接到这个任务的时候,觉得很难,无从下手。接着就在网上查资料,查来查去都没有查到有用的信息。  没办法,问下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就可以搞定。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值