Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1306845
  • 博文数量: 860
  • 博客积分: 425
  • 博客等级: 下士
  • 技术积分: 1464
  • 用 户 组: 普通用户
  • 注册时间: 2011-08-20 19:57
个人简介

对技术执着

文章分类

全部博文(860)

文章存档

2019年(16)

2018年(12)

2015年(732)

2013年(85)

2012年(15)

我的朋友

分类: LINUX

2015-07-08 17:56:17

1         General

1 编译release版本

2 除报错信息外,去除所有的打印信息,对于部分常规错误,也可以关闭打印信息

3 Config.java

public static final boolean LOGD =true;  改为:

public static final boolean LOGD =false;

很多打印信息都通过判断LOGD来决定是否输出。

很可能改成release版本后这条就无效了。

4 减少不必要的APK安装

5 缩减开机动画和铃声

6 超频?

7 AT和MODEM的交互的优化

8 去除proload-classes

9 裁减服务

10 先进lounch,再加载东西

11 运行时把有些刷新时间拉长点

12 动态壁纸去掉

13 TP校准apk去掉

14 视频播放器保留一个即可

15 去掉一些定时器

16 配置文件去掉不需要的配置,其他都关闭debug

17 优化屏幕刷新速度

18 调整堆的大小



2         开机速度

2.1        浅谈Android开机启动速度优化(含应用程序启动速度优化)

http://blog.csdn.net/jackyu613/article/details/6044297


众所周知Android开机启动速度较慢,于是如何加快启动速度便成为一个值得讨论的问题。


在查阅过许多资料后(特别是GoogleGroup的android-platform),我整理总结出下面几点基本看法。


Android开机启动耗时较多的部分有2个,分别是preloadclasses和scan packages。


这里又数preloadclasses最为耗时,在我的机子上一般需要13秒左右。关于preloadclasses的优化,可以参见。这篇帖子并没有给出如何优化preloaded-classes list的具体取舍。实际上,在看过google group众多关于preload class的主题后,基本可以确定以下事实:


preloaded-classes list中预加载的类位于dalvik zygote进程的heap中。在zygote衍生一个新的dalvik进程后,新进程只需加载heap中没有预加载的类(这些后加载进来的类成为该进程所private独有的),这样便加快了应用程序的启动速度。实际上这是一种以空间换时间的办法,因为几乎没有一个应用程序能够使用到所有的预加载类,必定有很多类对于该应用程序来说是冗余的。但是也正如Google所说,智能手机开机远没有启动应用程序频繁——用户开机一次,但直到下次再开机之前可能要运行多个应用程序。因此牺牲一点启动时间来换取应用程序加载时的较快速度是合算的。

preloaded-classes list已经是Google Android工程师使用众多测试工具分析,加以手动微调后形成的最优化预加载列表,涵盖了智能机上最长见的应用类型所需要的各种类。很难想象我们自己能够有什么手段能够获得比这样更优的一个预加载列表。所以,除非你的Android系统是被移植到非智能手机设备上使用(例如MID、EBOOK,可以不需要Telephony相关的类),不建议去“优化”preloaded-classes list。

在zygote中单起一个线程来做preload,是否可行?答案是否定的。首先在zygote中不可以新开线程,其次,就算新开一个线程,在目前智能机硬件条件下(单核CPU),除非有频繁大量的存储IO,否则我们不能看到我们期望加速启动效果。

关于scanpackages的问题。同样参考上面提到的那篇帖子,我们从中可以知道一个事实:越少的apk安装,越短的启动时间。事实上确实如此,apk安装的多少的确影响开机速度,但相比而言,scan packages所花费的时间远没有preload classe多。似乎这里没有多少油水可榨,但起码我们知道了:尽量减少产品中预置的apk数量可以提升启动速度(哪怕精简到极致也许只节省了2s)。


最后,关于那篇帖子中提到的startservices阶段,我认为虽然此阶段确实需要消耗可观的时间,但是正如文中提到的那样,优化这些services其实就是剔除我们不需要的一些services,而且不仅仅是修改SystemServer.java的问题,任何使用到被优化剔除掉的服务的代码都必须加以修改,否则系统肯定是起不来的。这样工作量大,而且难度也不小,并且有一定风险。因此对这些services的优化要慎之又慎。


那么加快启动速度是不是就没有办法了呢?也不是。除了硬件上的改动,在软件上使用BLCR技术也可以解决这个问题。在http://blog.csdn.net/shuaiff/archive/2010/09/19/5894646.aspx这篇文章中比较详细的介绍了BLCR技术在Android上的应用情况。个人认为应用BLCR不复杂,值得我们尝试。


在此我认为同时有必要提一下应用程序启动速度加速的问题。用过Android的都会发现,第一次启动某个应用程序时比较慢,但只要不关机重启,大部分情况下以后再次启动就明显的要快许多。因此我们很容易想到一种办法,即“预加载”我们的应用程序一次,那么下次用户再次启动我们时不就快了吗?


我们首先明确一点:任何“预加载”的想法都是不切实际的。先不讨论实施在技术上的可能性,我们只要看一下Android的Activity生命周期管理就应该明白,就算你通过某种方式“预加载”了你的某个Activity,你也不能确保在用户真正要求开始运行它的时候,你所“预加载”的Activity还存在,因为Android很可能在你为“预加载”第一次启动Activity后的不久就将它gc掉了。依靠一个不可靠的技术,显然是不明智的。


那么还有没有别的办法呢?答案是有的,但是只在少数情况下才有一定意义。在源码的frameworks/base/core/res/res/values/arrays.xml中,我们可以看到有名为“preloaded_drawables”的项,其中列出的是Android在启动时预加载的图形资源,这样在某个应用程序需要这些图形资源时就不必再加载了。如果我们某个应用程序包含大量的图形资源,那么我们可以将其加入到这个preloaded_drawables项中以加快我们应用程序的启动速度。但是这样有一个显而易见的弊端:同preload classes一样,不是每个应用程序都需要所有预加载的图形资源,这些冗余的资源反而占据了应用程序进程的内存空间。因此,这种技术实际应用的局限性较大,仅限于这样一种情况:某个设备只运行固定的几个应用程序,而且这些应用程序包含大量的图形资源需要加载。但这样会是一个什么设备呢?


好了,到此基本上把我这两天研究的心得写出来了。限于认识水平有限,如果文中有误或者哪位能有更好的想法,欢迎在下面留言:)如果以后我又有心得,会再更新此文。


2.2        Android重量级开发之--提高android启动速度研究

   首发,作者:Tigertang2@gmail.com


     大家都知道启动速度慢是智能操作系统的一个通病,Android也不例外,启动速度大概在1分钟左右,虽然日本有一个叫quick boot的一秒启动android的产品,但是毕竟是旁门左道。所以从常规来提高android的启动速度成了大家研究的重点,也是难点。下面将初步研究的一下经验跟大家分享一下。


首先看一下android系统的启动流程:


bootloader

         引导程序


kernel

        内核


init

         init初始化(这个大家都比较熟悉了,不要多说)


loads several daemons and services, includingzygote

see /init.rc and init.<platform>.rc


zygote


这个是占用时间最多的,重点修理对象

preloads classes

装载了一千多个类,妈呀!!!

starts package manager 扫描package(下面详细介绍)

service manager


start services (启动多个服务)


从实际的测试数据来看,有两个地方时最耗时间的,一个是zygote的装载一千多个类和初始化堆栈的过程,用了20秒左右。另一个是扫描


/system/app,

   /system/framework,

   /data/app,

   /data/app-private.


这几个目录下面的package用了大概10秒,所以我们重点能够修理的就是这两个老大的。


一、首先是调试工具的使用,可以测试哪些类和那些过程占用了多少时间,


主要工具为


stopwatch


Message loggers


grabserial

参考


printk times 参考

logcat

Android自带


bootchart 参考 和


       


strace


   AOSP的一部分(Eclair及以上版本)


使用例子


在init.rc中为了调试zygote


service zygote /system/bin/app_process-Xzygote /system/bin --zygote --start-system-server改为

service zygote /system/xbin/strace -tt-o/data/boot.strace /system/bin/app_process -Xzygote /system/bin --zygote--start-system-server


method tracer*


ftrace*


详细使用可看提供的文档和网页介绍


上面的工具如果不用详细的分析不一定都用到,也可以使用logcat就可以,在代码中加一点计算时间和一些类的调试信息也可以达到很好效果。


二、zygote 装载1千多个类


首先,我们可以添加一点调试信息,以获得具体转载情况。


diff --gita/core/java/com/android/internal/os/ZygoteInit.javab/core/java/com/android/internal/os/ZygoteInit.java

index 404c513..f2b573c 100644

--- a/core/java/com/android/internal/os/ZygoteInit.java

+++b/core/java/com/android/internal/os/ZygoteInit.java

@@ -259,6 +259,8 @@ public class ZygoteInit{

        } else {

            Log.i(TAG, "Preloading classes...");

            long startTime = SystemClock.uptimeMillis();

+            long lastTime =SystemClock.uptimeMillis();

+           long nextTime = SystemClock.uptimeMillis();


            // Drop root perms while running static initializers.

            setEffectiveGroup(UNPRIVILEGED_GID);

@@ -292,12 +294,24 @@ public classZygoteInit {

                         if (Config.LOGV) {

                             Log.v(TAG,"Preloading " + line + "...");

                         }

+                        //if (count%5==0) {

+                        //    Log.v(TAG, "Preloading " + line +"...");

+                        //}

+                        Log.v(TAG,"Preloading " + line + "...");

                         Class.forName(line);

+              nextTime =SystemClock.uptimeMillis();

+  if (nextTime-lastTime >50) {

+       Log.i(TAG, "Preloading " + line+ "... took " + (nextTime-lastTime) + "ms.");

+   }

+  lastTime = nextTime;

                         if(Debug.getGlobalAllocSize() > PRELOAD_GC_THRESHOLD) {

                             if (Config.LOGV) {

                                Log.v(TAG,

                                     " GCat " + Debug.getGlobalAllocSize());

                             }

+                            Log.i(TAG,

+                               " GC at" + Debug.getGlobalAllocSize());

                            runtime.gcSoftReferences();

                            runtime.runFinalizationSync();

                            Debug.resetGlobalAllocSize();


上面+代表添加的代码,这样就可以很容易的得到在装载类的过程中具体装载了哪些类,耗费了多久。具体装载的类在文件platform/frameworks/base/     preloaded-classes


内容类似:


android.R$styleable

android.accounts.AccountMonitor

android.accounts.AccountMonitor$AccountUpdater

android.app.Activity

android.app.ActivityGroup

android.app.ActivityManager$MemoryInfo$1

android.app.ActivityManagerNative

android.app.ActivityManagerProxy

android.app.ActivityThread

android.app.ActivityThread$ActivityRecord

android.app.ActivityThread$AppBindData

android.app.ActivityThread$ApplicationThread

android.app.ActivityThread$ContextCleanupInfo

android.app.ActivityThread$GcIdler

android.app.ActivityThread$H

android.app.ActivityThread$Idler


而这个文件是由文件WritePreloadedClassFile.java中的WritePreloadedClassFile类自动生成


/**


* Writes/frameworks/base/preloaded-classes. Also updates


* {@link LoadedClass#preloaded} fields andwrites over compiled log file.

*/

public class WritePreloadedClassFile

   /**

    * Preload any class that take longer to load than MIN_LOAD_TIME_MICROSus.

    */


static final int MIN_LOAD_TIME_MICROS =1250;//这个代表了装载时间小于1250us即1.25ms的类将不予装载,也许可以改这个参数减少一下类的装载


//这里可以看到什么样的类会被装载


A:启动必须装载的类,比如系统级的类

B:刚才说的装载时间大于1.25ms的类

C:被使用一次以上或被应用装载的类


仔细看看筛选类的具体实现,可以帮助我们认识哪些类比较重要,哪些可以去掉。

筛选规则是

第一  isPreloadable,


   /**Reports if the given class should be preloaded. */

   public static boolean isPreloadable(LoadedClass clazz) {


       return clazz.systemClass && !EXCLUDED_CLASSES.contains(clazz.name);

    }


意思是指除了EXCLUDED_CLASSES包含的类之外的所有系统装载的类。


EXCLUDED_CLASSES包含


   /**

    * Classes which we shouldn't load from the Zygote.

    */

   private static final Set<String> EXCLUDED_CLASSES

           = new HashSet<String>(Arrays.asList(

        // Binders

       "android.app.AlarmManager",

       "android.app.SearchManager",

       "android.os.FileObserver",

       "com.android.server.PackageManagerService$AppDirObserver",


       // Threads

       "android.os.AsyncTask",

       "android.pim.ContactsAsyncHelper",

       "java.lang.ProcessManager"

   ));


目前是跟Binders跟Threads有关的不会被预装载。


第二   clazz.medianTimeMicros() >MIN_LOAD_TIME_MICROS装载时间大于1.25ms。


第三  names.size() > 1 ,既是被processes一次以上的。


上面的都是指的systemclass,另外还有一些applicationclass需要被装载


规则是fromZygote而且不是服务


proc.fromZygote() &&!Policy.isService(proc.name)


fromZygote指的除了com.android.development的zygote类


   public boolean fromZygote() {

       return parent != null && parent.name.equals("zygote")

                &&!name.equals("com.android.development");

    }


/除了常驻内存的服务


   /**

    * Long running services. These are restricted in their contribution tothe

    * preloader because their launch time is less critical.

    */

   // TODO: Generate this automatically from package manager.

   private static final Set<String> SERVICES = new HashSet<String>(Arrays.asList(

       "system_server",

       "com.google.process.content",

       "android.process.media",

       "com.android.bluetooth",

       "com.android.calendar",

       "com.android.inputmethod.latin",

       "com.android.phone",

        "com.google.android.apps.maps.FriendService",// pre froyo

       "com.google.android.apps.maps:FriendService", // froyo

       "com.google.android.apps.maps.LocationFriendService",

       "com.google.android.deskclock",

       "com.google.process.gapps",

       "android.tts"

   ));


好了。要转载的就是这些类了。虽然preloaded-classes是在下载源码的时候已经确定了的,也就是对我们来说WritePreloadedClassFile类是没用到的,我们可以做的就是在preloaded-classes文件中,把不预装载的类去掉,试了把所有类去掉,启动确实很快跳过那个地方,但是启动HOME的时候就会很慢了。所以最好的方法就是只去掉那些没怎么用到的,不过要小心处理。至于该去掉哪些,还在摸索,稍后跟大家分享。有兴趣的朋友可以先把preloaded-classes这个文件里面全部清空,启动快了很多,但在启动apk的时候会慢了点。当然了,也可以把android相关的类全部去掉,剩下java的类,试过了也是可以提高速度。


三,系统服务初始化和package扫描


在启动系统服务的init2()时会启动应用层(Java层)的所有服务。


   public static void main(String[] args) {

        System.loadLibrary("android_servers");

       init1(args); //init1 初始化,完成之后会回调init2()

    }


在init2()中会启动一个线程来启动所有服务


public static final void init2() {

       Log.i(TAG, "Entered the Android system server!");

       Thread thr = new ServerThread();

       thr.setName("android.server.ServerThread");

       thr.start();

    }


class ServerThread extends Thread {


。。。


public void run() {


。。。


关键服务:


ServiceManager.addService("entropy", new EntropyService());


ServiceManager.addService(Context.POWER_SERVICE,power);


  context = ActivityManagerService.main(factoryTest);


  ServiceManager.addService("telephony.registry",new TelephonyRegistry(context));


   PackageManagerService.main(context,

                    factoryTest !=SystemServer.FACTORY_TEST_OFF);//apk扫描的服务


  ServiceManager.addService(Context.ACCOUNT_SERVICE,

                        newAccountManagerService(context));


        ContentService.main(context,

                    factoryTest ==SystemServer.FACTORY_TEST_LOW_LEVEL);


      battery = new BatteryService(context);

           ServiceManager.addService("battery", battery);


       hardware = new HardwareService(context);

           ServiceManager.addService("hardware", hardware);


         AlarmManagerService alarm = new AlarmManagerService(context);

           ServiceManager.addService(Context.ALARM_SERVICE, alarm);


ServiceManager.addService(Context.SENSOR_SERVICE,new SensorService(context));


WindowManagerService.main(context, power,

                    factoryTest !=SystemServer.FACTORY_TEST_LOW_LEVEL);

           ServiceManager.addService(Context.WINDOW_SERVICE, wm);


上面这些都是关键服务,不建议进行裁剪。


下面的这些不是很关键,可以进行裁剪,当是必须相应的修改framework部分的代码,工作量比较大和复杂。我去掉了20个服务,大概需要相应修改大概20多个文件。


                statusBar = newStatusBarService(context);

               ServiceManager.addService("statusbar", statusBar);

     

                ServiceManager.addService("clipboard",new ClipboardService(context));

     

                imm = newInputMethodManagerService(context, statusBar);

               ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);

     

                ServiceManager.addService("netstat",new NetStatService(context));

     

                connectivity =ConnectivityService.getInstance(context);


               ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);

                   ServiceManager.addService(Context.ACCESSIBILITY_SERVICE,

                      newAccessibilityManagerService(context));

      

                notification = newNotificationManagerService(context, statusBar, hardware);

                ServiceManager.addService(Context.NOTIFICATION_SERVICE,notification);

    

               ServiceManager.addService("mount", new MountService(context));


               ServiceManager.addService(DeviceStorageMonitorService.SERVICE,

                        new DeviceStorageMonitorService(context));

    

               ServiceManager.addService(Context.LOCATION_SERVICE, newLocationManagerService(context));


                ServiceManager.addService(Context.SEARCH_SERVICE, new SearchManagerService(context) );

 

           if (INCLUDE_DEMO) {

                Log.i(TAG, "Installingdemo data...");

                (newDemoThread(context)).start();

           }


                Intent intent = newIntent().setComponent(new ComponentName(

                        "com.google.android.server.checkin",

                       "com.google.android.server.checkin.CheckinService"));

      

                   ServiceManager.addService("checkin", newFallbackCheckinService(context));


                wallpaper = new WallpaperManagerService(context);

               ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);

          

               ServiceManager.addService(Context.AUDIO_SERVICE, newAudioService(context));

    

                headset = new HeadsetObserver(context);

  

                dock = newDockObserver(context, power);

     

               ServiceManager.addService(Context.BACKUP_SERVICE, newBackupManagerService(context));

  

               ServiceManager.addService(Context.APPWIDGET_SERVICE, appWidget);


package 扫描部分,整个流程为下图所示:


最终的zip文件(apk)读取是在下面这两个函数:


/*

* Open the specified file read-only.  We memory-map the entire thing and

* close the file before returning.

*/

status_t ZipFileRO::open(const char*zipFileName)

{

   int fd = -1;

   off_t length;


   assert(mFileMap == NULL);


LOGD("opening zip '%s'\n",zipFileName);


   /*

    * Open and map the specified file.

    */


   fd = ::open(zipFileName, O_RDONLY);


   if (fd < 0) {

       LOGW("Unable to open zip '%s': %s\n", zipFileName,strerror(errno));

        return NAME_NOT_FOUND;

    }


   length = lseek(fd, 0, SEEK_END);

   if (length < 0) {

       close(fd);

       return UNKNOWN_ERROR;

    }


   mFileMap = new FileMap();


   if (mFileMap == NULL) {

       close(fd);

       return NO_MEMORY;

    }


   if (!mFileMap->create(zipFileName, fd, 0, length, true)) {

       LOGW("Unable to map '%s': %s\n", zipFileName,strerror(errno));

       close(fd);

       return UNKNOWN_ERROR;

    }


   mFd = fd;


   /*

    * Got it mapped, verify it and create data structures for fast access.

    */

   if (!parseZipArchive()) {

       mFileMap->release();

       mFileMap = NULL;

       return UNKNOWN_ERROR;

    }


LOGD("done opening zip\n");


   return OK;

}


/*

* Parse the Zip archive, verifying itscontents and initializing internal

* data structures.

*/

bool ZipFileRO::parseZipArchive(void)

{

#define CHECK_OFFSET(_off) {                                               \

       if ((unsigned int) (_off) >= maxOffset) {                           \

            LOGE("ERROR: bad offset %u (max%d): %s\n",                     \

                (unsigned int) (_off),maxOffset, #_off);                   \

           goto bail;                                                     \

       }                                                                   \

    }



   const unsigned char* basePtr = (const unsignedchar*)mFileMap->getDataPtr();

   const unsigned char* ptr;

   size_t length = mFileMap->getDataLength();

   bool result = false;

   unsigned int i, numEntries, cdOffset;

   unsigned int val;


   /*

    * The first 4 bytes of the file will either be the local header

    * signature for the first file (kLFHSignature) or, if the archivedoesn't

    * have any files in it, the end-of-central-directory signature

    * (kEOCDSignature).

    */

   val = get4LE(basePtr);

   if (val == kEOCDSignature) {

       LOGI("Found Zip archive, but it looks empty\n");

       goto bail;

    }else if (val != kLFHSignature) {

       LOGV("Not a Zip archive (found 0x%08x)\n", val);

       goto bail;

    }


   /*

    * Find the EOCD.  We'll find itimmediately unless they have a file

    * comment.

    */

   ptr = basePtr + length - kEOCDLen;


   while (ptr >= basePtr) {

       if (*ptr == (kEOCDSignature & 0xff) && get4LE(ptr) ==kEOCDSignature)

           break;

       ptr--;

    }

   if (ptr < basePtr) {

       LOGI("Could not find end-of-central-directory in Zip\n");

       goto bail;

    }


   /*

    * There are two interesting items in the EOCD block: the number of

    * entries in the file, and the file offset of the start of the

    * central directory.

    *

    * (There's actually a count of the #of entries in this file, and for

    * all files which comprise a spanned archive, but for our purposes

    * we're only interested in the current file.  Besides, we expect the

    * two to be equivalent for our stuff.)

    */

   numEntries = get2LE(ptr + kEOCDNumEntries);

   cdOffset = get4LE(ptr + kEOCDFileOffset);


   /* valid offsets are [0,EOCD] */

   unsigned int maxOffset;

   maxOffset = (ptr - basePtr) +1;


   LOGV("+++ numEntries=%d cdOffset=%d\n", numEntries, cdOffset);

   if (numEntries == 0 || cdOffset >= length) {

       LOGW("Invalid entries=%d offset=%d (len=%zd)\n",

           numEntries, cdOffset, length);

       goto bail;

    }


   /*

    * Create hash table.  We have aminimum 75% load factor, possibly as

    * low as 50% after we round off to a power of 2.

    */

   mNumEntries = numEntries;

   mHashTableSize = roundUpPower2(1 + ((numEntries * 4) / 3));

   mHashTable = (HashEntry*) calloc(1, sizeof(HashEntry) * mHashTableSize);


   /*

    * Walk through the central directory, adding entries to the hash

    * table.

    */

   ptr = basePtr + cdOffset;

   for (i = 0; i < numEntries; i++) {

       unsigned int fileNameLen, extraLen, commentLen, localHdrOffset;

       const unsigned char* localHdr;

       unsigned int hash;


       if (get4LE(ptr) != kCDESignature) {

           LOGW("Missed a central dir sig (at %d)\n", i);

           goto bail;

       }

       if (ptr + kCDELen > basePtr + length) {

           LOGW("Ran off the end (at %d)\n", i);

           goto bail;

       }


       localHdrOffset = get4LE(ptr + kCDELocalOffset);

        CHECK_OFFSET(localHdrOffset);

       fileNameLen = get2LE(ptr + kCDENameLen);

       extraLen = get2LE(ptr + kCDEExtraLen);

       commentLen = get2LE(ptr + kCDECommentLen);


       //LOGV("+++ %d: localHdr=%d fnl=%d el=%d cl=%d\n",

       //    i, localHdrOffset, fileNameLen, extraLen,commentLen);

       //LOGV(" '%.*s'\n", fileNameLen, ptr + kCDELen);


       /* add the CDE filename to the hash table */

       hash = computeHash((const char*)ptr + kCDELen, fileNameLen);

       addToHash((const char*)ptr + kCDELen, fileNameLen, hash);


     //  localHdr = basePtr +localHdrOffset;

     //  if (get4LE(localHdr) !=kLFHSignature) {

          // LOGW("Bad offset to local header: %d (at %d)\n",

            //   localHdrOffset, i);

         //  goto bail;

    //   }


       ptr += kCDELen + fileNameLen + extraLen + commentLen;

       CHECK_OFFSET(ptr - basePtr);

    }

   result = true;

bail:

   return result;

#undef CHECK_OFFSET

}

     红色部分是修改后的代码,大家可以对比一下。(未完。。。)


参考资料推荐:

           


2.3        利用BLCR加快android的启动过程

作者:帅文


摘要:介绍了利用blcr对android启动速度进行优化的原理、实施步骤和注意点,在虚拟机上验证获得缩短10秒以上启动时间。

关键词:blcr  android  启动速度  zygote   类加载

引言 随着google的android操作系统在手机、平板电脑等领域大量使用,android的启动速度慢也成为许多使用者抱怨的缺点。相比手机这类平时较少开关机的设备,平板电脑在实际使用中开关的频率相对频繁,开机速度是影响客户感受的一个重要因素。Android启动过程分为linux内核载入,文件系统挂接,zygote进程启动和软件包扫描几个主要过程。其中耗时大户发生在zygote的类载入和软件包扫描过程两处,只要减少这两处的时间,启动速度就会发生明显的改变。如何有效加快启动速度是众多android产品制造者都感兴趣的技术。本文就缩短android 启动过程中公用类加载部分的一种技术做介绍,利用该方法,在虚拟机实际测试获得良好的结果。

背景知识 Zygote是android中的核心进程,其负责android其他应用和服务的孵化,zygote启动过程慢的一个原因在于

启动过程中需要提前加载公用类(由文件preloaded-classes定义),这种加载是android设计人员特意根据linux和嵌入式系统特性设计的,网络上有人尝试将这些类的加载去除以加快开机速度,这种违背设计者初衷的方法被否决(参考1)。对于单个进程而言,如果使用到这些公共类,都必须完成对应类的载入并初始化,由于zygote是后续所有android的父进程,采用Class.forName处理的公共类会被载入到内存并完成静态初始化,提前加载可以避免每个子进程调用时候需要生成公用类的副本(linux Copy-on-write特点),进而减少内存占用量以及后续启动其他程序的花销。这个加载过程关系到后续的性能,所以不能简单的跳过。载入的过程主要是对内存的操作过程,其中包括了大量的内存分配释放过程,该过程由于有上千个类需要操作而变得耗时较长。在实际应用中,由于framework部分较少升级,故这些公共类是不会被动态删减,考虑到这些特点,采用checkpoint方式每次直接将zygote还原到完成类加载的阶段避开频繁的类操作显然可以提高速度。

BLCR (BerkeleyLab Checkpoint/Restart)是应用于linux下的checkpoint/restore软件,它可以将正在运行于linux上的应用当前的运行点保存成为一个文件并且在以后的时间可以按照需要将该程序直接恢复到保存时候的状态。该软件官方网站在: 实施过称

一 软件准备

blcr-0.8.2

android2.2froyo

android-goldfish-2.6.29

编译android和内核在这里不详述,需要指出的有两点。

1 编译内核时候注意加入可加载模块支持(Enable loadablemodule support),缺省的goldfish内核配置是不支持的。

2 需要对android的bionic的线程库进行扩展,扩展方法是采用上述软件的pthread文件替换相应线程库文件。

二 编译blcr的内核驱动模块和应用直接使用android内建的编译器即可对内核驱动进行编译。编译过程只要指定正确的编译器路径和内核路径即可顺利编译。生成的内核可加载模块分别是:

cr_module/kbuild/blcr.ko

blcr_imports/kbuild/blcr_imports.ko

顺利编译blcr应用部分需要使用打过扩展补丁的pthread。补丁方法为直接取代bionic目录下对应文件,并且修改bionic/libc/Android.mk,开启对应的编译开关,在libc_common_cflags加入-DUCLIBC_LINUXTHREAD_OLD_EXTENSTION宏。

Blcr文件也需要如下修改才能正常编译和运行。

其中文件libcr/cr_libinit.c:

rc = __cri_ksigaction(signum, (act?&ksa : NULL), (oact ? &oksa : NULL), (_NSIG/8), &errno);改动为:

rc = __cri_ksigaction(signum, (act?&ksa : NULL), (oact ? &oksa : NULL), (_NSIG/4), &errno);

将_NSIG/8改动为_NSIG/4修改的原因为在bionic中定义NSIG=32,而内核为64,造成kernel/signal.c中rt_sigaction调用认为参数错误而返回错误。在android系统中对应非prelink的动态库调用dlopen(NULL, RTLD_LAZY);(即查找自己)会异常,需要屏蔽cr_libinit.c函数cri_init对dlopen的调用。

在blcr工程中加入Android.mk,需要注意的是下面几个文件需要编译为arm而非缺省的thumb指令:cr_async.c. cr_core.c cr_sig_sync.c  cr_cs.c  cr_syscall.c,否则编译无法通过。

三  zygote加入checkpoint支持 当系统启动zygote服务时候先判断是否存在checkpoint文件,如果有则调用cr_restart载入保存的checkpoint文件,否则按照正常的zygote流程进行。由于zygote是其他android的父进程,其生成的许多进程/线程都有socket等checkpoint无法恢复的限制因素,故不适合将checkpoint放到zygote启动过后点,本文选择在ZygoteInit.java的main后面的preloadResources()完成后进行。这个过程刚好加载完毕耗时长的公用类而且基本没有使用很多限制资源。原生的android在preload前有创建socket动作,可以调整到preloadResources后面,调整后的样子如下(斜体代码为调整顺序部分):

CheckPoint cp=newCheckPoint();

try {

   //Start profiling thezygote initialization.

SamplingProfilerIntegration.start();

  preloadClasses();

  preloadResources();

cp.checkPoint("/data/zygote.blcr");

  registerZygoteSocket();

其中CheckPoint 类是c扩展的java调用接口,通过jni调用checkpoint库函数实现checkpoint动作,下文列出了jni部分内容。该部分仅为一个参考模板,产品化过程应该做更多异常处理和合理化调整工作。

android_blcr_checkpoint.cpp

#defineLOG_TAG "BLCR"

namespaceandroid {

staticint my_callback(void* arg)

{

   int rc;

    LOGV(__FUNCTION__);

    rc =cr_checkpoint(0);

return 0;

}

staticvoid checkPoint(JNIEnv* env,jobject object,jstring file) {

    LOGV(__FUNCTION__);

     pid_t my_pid;

      intrc,fd;

    struct stat s;

   cr_checkpoint_handle_tmy_handle;

    cr_callback_id_t cb_id;

     FILE *f;

     const jchar*str =env->GetStringCritical(file, 0);

    String8 filename;

    if (str) {

       filename = String8(str,env->GetStringLength(file));

       env->ReleaseStringCritical(file,str);

       }

       else {

              LOGE("checkPoint,file nameis null");

                   return;

           }

   LOGI("checkPoint,filename=%s",filename);

   //does the file exist?

    f = fopen(filename,"r");

     if(f==NULL)//create and save checkpointto it.

       {

       my_pid = cr_init();

         if(my_pid<0)

                {

                 LOGE("cr_initfailed,return:%d",my_pid);

                        return;

                }

       cb_id =cr_register_callback(my_callback, NULL, CR_SIGNAL_CONTEXT);

       if (cb_id < 0)

                {

             LOGV("cr_register_callback()unexpectedly returned %d/n",cb_id);

             return;

           }

      else

          {

         LOGV("cr_register_callback() correctly returned%d/n", cb_id);

          }

    

      /*Request a checkpoint of ourself */

     fd=crut_checkpoint_request(&my_handle, filename);

    if (fd < 0)

         {

        LOGE("crut_checkpoint_request() unexpectedly returned0x%x/n",fd);

        return ;

         }

    rc = stat(filename,&s);

    if (rc) {

      LOGE("stat() unexpectedly returned%d/n", rc);

      return;

    } else {

      LOGV("stat(context.%d) correctlyreturned 0/n", my_pid);

    }

    if (s.st_size == 0) {

      LOGE("context file unexpectedlyempty/n");

      return;

    } else {

      LOGV("context.%d isnon-empty/n", my_pid);

    }

    /* Reap thecheckpoint request */

    rc =crut_checkpoint_wait(&my_handle,fd);

    if (rc < 0) {

      LOGE("crut_checkpoint_wait() #1unexpectedly returned 0x%x/n", rc);

      return;

   }    }}

/*

* JNI registration.

*/

staticJNINativeMethod gMethods[] = {

    /* name, signature,funcPtr */

      {"checkPoint","(Ljava/lang/String;)V", (void *)checkPoint},

};


intregister_android_blcr_checkpoint(JNIEnv*env)

{

    returnjniRegisterNativeMethods(env,"android/blcr/CheckPoint",

       gMethods, NELEM(gMethods));

}

}

为了正常使用上面的jni接口,需要把注册函数注册到frameworks/base/core/jni/ AndroidRuntime.cpp,在gRegJNI[]中加入REG_JNI(register_android_blcr_checkpoint)。同时创建CheckPoint.java 文件提供给java调用:

packageandroid.blcr;

publicclass CheckPoint {

    public CheckPoint() {

    }

    public native voidcheckPoint(String fileName);

}


正常启动blcr还需要修改init.rc,调整启动zygote的方式。修改点包括载人blcr内核驱动:

insmod/system/lib/modules/blcr_imports.ko

insmod/system/lib/modules/blcr.ko

和启动blcr服务方式,将

servicezygote /system/bin/app_process-Xzygote /system/bin --zygote--start-system-server

替换为:servicezygote/system/bin/quick.sh -Xzygote /system/bin --zygote --start-system-server

其中quick.sh脚本内容为:

#!/system/bin/busybox sh

ec()

{

/system/bin/busybox echo $*>/dev/console;

}

if [ -f /data/zygote.blcr ]; then

  ec"***************load savedzygote********************"

/system/bin/cr_restart -f/data/zygote.blcr ;

else

  ec"optimised by blcr"

  ec"**************start a newzygote******************"

  ec"you can contact theauthor through Email:shuaiwen@yahoo.com.cn"

  ec"this is only an originaldemon version"

  ec"release pid:$$."

/system/bin/cr_checkpoint

  ec"release pid:$$."

/system/bin/cr_checkpoint

  ec"release pid:$$."

/system/bin/app_process  $*

fi

需要指出的是运行上面几个不带参数的cr_checkpoint目的为将后面的zygote的pid退后一些,以防下次checkpoint时候遇到pid冲突而导致checkpoint失败。

加入该功能后当系统首次启动会创建/data/zygote.blcr文件,由于需要checkpoint,导致比正常启动时间慢较多。后续只要该文件存在,系统就会跨过类加载过程,进而加快启动速度。

4 测试验证

下表列出了虚拟机加入blcr前和blcr后的10次的启动速度。

Emulator启动参数如下:

emulator.exe  -kernel zImage-show-kernel -partition-size200 -memory 200 -skindir ./skins -skin WQVGA432-shell -sysdir ./ -datauserdata.img -ramdisk ramdisk.img -system system.img-sdcard sdcard.img  -sdcard sdcard.img


模式

启动时间(单位:秒)

采用blcr

42

43

42

36

42

41

42

38

39

43

正常启动

64

63

50

57

65

54

52

45

53

49

启用blcr后平均启动时间为41,相比正常启动平均时间55秒快了14秒。启动时间的改善是明显的。



参考1:

附部分android.mk文件


cat ./libcr/Android.mk

# Copyright 2006 The Android Open SourceProject


LOCAL_PATH := $(my-dir)


include $(CLEAR_VARS)

LOCAL_SHARED_LIBRARIES := libcutils

LOCAL_SHARED_LIBRARIES += libdl

LOCAL_SHARED_LIBRARIES += libc

lib_SOURCES = /

cr_omit.c


LOCAL_SRC_FILES:= $(lib_SOURCES)


LOCAL_C_INCLUDES := /

$(KERNEL_HEADERS) /

$(LOCAL_PATH)/../include /

$(LOCAL_PATH)/../    /

     $(LOCAL_PATH)/arch/arm/


LOCAL_CFLAGS := -DHAVE_CONFIG_H-DSHUAIWEN

LOCAL_PRELINK_MODULE := false

LOCAL_MODULE := libcr_omit

include $(BUILD_SHARED_LIBRARY)


#libcr_run.so

include $(CLEAR_VARS)

LOCAL_SHARED_LIBRARIES := libcutils

LOCAL_SHARED_LIBRARIES += libdl

LOCAL_SHARED_LIBRARIES += libc

lib_SOURCES = /

            cr_run.c


LOCAL_SRC_FILES:= $(lib_SOURCES)


LOCAL_C_INCLUDES := /

$(KERNEL_HEADERS) /

$(LOCAL_PATH)/../include /

$(LOCAL_PATH)/../    /

     $(LOCAL_PATH)/arch/arm/


LOCAL_CFLAGS := -DHAVE_CONFIG_H-DSHUAIWEN

LOCAL_PRELINK_MODULE := false

LOCAL_MODULE := libcr_run

include $(BUILD_SHARED_LIBRARY)


#libcr

include $(CLEAR_VARS)

LOCAL_SHARED_LIBRARIES := libcutils

LOCAL_SHARED_LIBRARIES += libdl

LOCAL_SHARED_LIBRARIES += libc

lib_SOURCES = /

             cr_async.c.arm /

             cr_trace.c /

             cr_core.c.arm  /

             cr_sig_sync.c.arm /

             cr_cs.c.arm  /

             cr_pthread.c /

             cr_strerror.c  /

             cr_request.c /

             cr_syscall.c.arm /

cr_omit.c   /

             cr_run.c


LOCAL_SRC_FILES:= $(lib_SOURCES)


LOCAL_C_INCLUDES := /

$(KERNEL_HEADERS) /

$(LOCAL_PATH)/../include /

$(LOCAL_PATH)/../    /

     $(LOCAL_PATH)/arch/arm/


LOCAL_CFLAGS := -DHAVE_CONFIG_H -DSHUAIWEN -g

LOCAL_PRELINK_MODULE := false

LOCAL_MODULE := libcr

include $(BUILD_SHARED_LIBRARY)


cat ./util/cr_restart/Android.mk

LOCAL_PATH := $(my-dir)


include $(CLEAR_VARS)

LOCAL_SHARED_LIBRARIES := libcutils

LOCAL_SHARED_LIBRARIES += libdl

LOCAL_SHARED_LIBRARIES += libc

LOCAL_SHARED_LIBRARIES += libcr_run

LOCAL_SHARED_LIBRARIES += libcr

LOCAL_SRC_FILES:=cr_restart.c

LOCAL_C_INCLUDES := /

$(KERNEL_HEADERS) /

$(LOCAL_PATH)/../../include /

     $(LOCAL_PATH)/arch/arm/


LOCAL_MODULE := cr_restart

include $(BUILD_EXECUTABLE)


cat ./util/cr_restart/Android.mk

LOCAL_PATH := $(my-dir)


include $(CLEAR_VARS)

LOCAL_SHARED_LIBRARIES := libcutils

LOCAL_SHARED_LIBRARIES += libdl

LOCAL_SHARED_LIBRARIES += libc

LOCAL_SHARED_LIBRARIES += libcr_run

LOCAL_SHARED_LIBRARIES += libcr

LOCAL_SRC_FILES:=cr_restart.c

LOCAL_C_INCLUDES := /

$(KERNEL_HEADERS) /

$(LOCAL_PATH)/../../include /

      $(LOCAL_PATH)/arch/arm/


LOCAL_MODULE := cr_restart

include $(BUILD_EXECUTABLE)

[root@wen blcr-0.8.2]#

[root@wen blcr-0.8.2]#

[root@wen blcr-0.8.2]#cat ./util/cr_checkpoint/Android.mk

LOCAL_PATH := $(my-dir)


include $(CLEAR_VARS)

LOCAL_SHARED_LIBRARIES := libcutils

LOCAL_SHARED_LIBRARIES += libdl

LOCAL_SHARED_LIBRARIES += libc

LOCAL_SHARED_LIBRARIES += libcr_run

LOCAL_SHARED_LIBRARIES += libcr

LOCAL_SRC_FILES:=cr_checkpoint.c

LOCAL_C_INCLUDES := /

$(KERNEL_HEADERS) /

$(LOCAL_PATH)/../../include /

      $(LOCAL_PATH)/arch/arm/


LOCAL_MODULE := cr_checkpoint

include $(BUILD_EXECUTABLE)



下面是crut_util_libcr.c,放到上面同一个目录下面。你也可以自己从blcr中复制出来。



/*

* Berkeley Lab Checkpoint/Restart(BLCR) for Linux is Copyright (c)

* 2008, The Regents of the University of California,through Lawrence

* Berkeley National Laboratory(subject to receipt of any required

* approvals from the U.S. Dept. ofEnergy).  All rights reserved.

*

* Portions may be copyrighted byothers, as may be noted in specific

* copyright notices withinspecific files.

*

* This program is free software;you can redistribute it and/or modify

* it under the terms of the GNUGeneral Public License as published by

* the Free Software Foundation;either version 2 of the License, or

* (at your option) any laterversion.

*

* This program is distributed inthe hope that it will be useful,

* but WITHOUT ANY WARRANTY;without even the implied warranty of

* MERCHANTABILITY or FITNESS FOR APARTICULAR PURPOSE.  See the

* GNU General Public License formore details.

*

* You should have received a copyof the GNU General Public License

* along with this program; if not,write to the Free Software

* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*

* $Id: crut_util_libcr.c,v 1.52008/12/26 10:50:35 phargrov Exp $

*

* Utility functions for BLCR tests(libcr-dependent portions)

*/


#define _LARGEFILE64_SOURCE 1   /*For O_LARGEFILE */

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <unistd.h>

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>


#include "crut_util.h"


/* checkpoint request/poll wrappers forsimpler code */


int

crut_checkpoint_request(cr_checkpoint_handle_t*handle_p, const char *filename) {

    int rc;

    cr_checkpoint_args_tmy_args;


    if (filename) {

        /*remove existing context file, if any */

       (void)unlink(filename);


        /* openthe context file */

        rc =open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE, 0600);

    } else {

/* NULL -> /dev/null */

        rc =open("/dev/null", O_WRONLY | O_LARGEFILE);

    }

    if (rc < 0) {

       perror("open");

        returnrc;

    }


   cr_initialize_checkpoint_args_t(&my_args);

    my_args.cr_fd = rc;/* still holds the return from open() */

    my_args.cr_scope =CR_SCOPE_PROC;


    /* issue the request*/

    rc =cr_request_checkpoint(&my_args, handle_p);

    if (rc < 0) {

       (void)close(my_args.cr_fd);

        if(filename) (void)unlink(filename);

       perror("cr_request_checkpoint");

        returnrc;

    }


    return my_args.cr_fd;

}


int

crut_checkpoint_wait(cr_checkpoint_handle_t*handle_p, int fd) {

    int rc, save_err;


    do {

        rc =cr_poll_checkpoint(handle_p, NULL);

        if (rc< 0) {

           if ((rc == CR_POLL_CHKPT_ERR_POST) && (errno == CR_ERESTARTED)) {

               /* restarting -- not an error */

               rc = 1; /* Signify RESTART to caller */

           } else if (errno == EINTR) {

               /* poll was interrupted by a signal -- retry */

continue;

           } else {

               /* return the error to caller */

               break;

           }

        } elseif (rc == 0) {

           fprintf(stderr, "cr_poll_checkpoint returned unexpected 0/n");

           rc = -1;

           goto out;

        } else{

           rc = 0; /* Signify CONTINUE to caller */

}

    } while (rc < 0);


    save_err = errno;

#if 0 // Nothing in the testsuite needsthis, but your APP might want it.

    (void)fsync(fd);

#endif

    (void)close(fd);

    errno = save_err;


out:

    return rc;

}


int

crut_checkpoint_block(const char*filename) {

   cr_checkpoint_handle_t my_handle;

    int ret, fd,save_err;


    fd =crut_checkpoint_request(&my_handle, filename);

    if (fd < 0) returnfd;


    ret =crut_checkpoint_wait(&my_handle, fd);


    save_err = errno;

    (void)close(fd);

    errno = save_err;


    return ret;

}



许多人问道关于编译内核模块的方法,这里我补充一下,比较简单的方法是建立一个脚本,比如叫run.sh,内容如下:

#!/bin/bash

curpath=`pwd`

#rm -rf `find ./ -name Makefile`

exportPATH=$PATH:/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin

TOOLCHAIN=/mydroid/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin

export ARCH=arm

./configure KCC=$TOOLCHAIN/arm-eabi-gcc LD=$TOOLCHAIN/arm-eabi-ld --with-linux-src=$curpath/../../kernel-common--with-linux=$curpath/../../kernel-common  host_alias=arm-linux

cd blcr_imports/

make

echo generated kernel module:`lskbuild/*.ko`

cd ../cr_module/

make

echo generated kernel module:`lskbuild/*.ko`

上面部分内容请根据自己的情况调整一下路径,比如上面的内核路径这样的东西。

全部工程我已经上传到csdn下载中,名称为:blcr-0.8.2-android  关键字:blcr  android   不需要资源分。


2.4        优化modem和主机的开机交互过程



3         运行速度

3.1        优化常驻内存的服务


3.2        



3.3        优化modem和主机的交互过程

(1) 提高可靠性,减少重发

(2) 对于部分频繁操作适当减少


4         编写高效的应用

4.1        编写高效的Android代码

Android, 电量, 节省, 速度, 代码本帖最后由 kernel 于 2009-7-1 15:51 编辑

eoeAndroid zhoubo5262


虽然如此说,但似乎并没有什么好的办法:Android设备是嵌入式设备。现代的手持设备,与其说是电话,更像一台拿在手中的电脑。但是,即使是“最快”的手持设备,其性能也赶不上一台普通的台式电脑。

这就是为什么我们在书写Android应用程序的时候要格外关注效率。这些设备并没有那么快,并且受电池电量的制约。这意味着,设备没有更多的能力,我们必须把程序写的尽量有效。

本章讨论了很多能让开发者使他们的程序运行更有效的方法,遵照这些方法,你可以使你的程序发挥最大的效力。

简介

对于占用资源的系统,有两条基本原则:

不要做不必要的事

不要分配不必要的内存


所有下面的内容都遵照这两个原则。

有些人可能马上会跳出来,把本节的大部分内容归于“草率的优化”(xing:参见[The Root ofAll Evil]),不可否认微优化(micro-optimization。xing:代码优化,相对于结构优化)的确会带来很多问题,诸如无法使用更有效的数据结构和算法。但是在手持设备上,你别无选择。假如你认为Android虚拟机的性能与台式机相当,你的程序很有可能一开始就占用了系统的全部内存(xing:内存很小),这会让你的程序慢得像蜗牛一样,更遑论做其他的操作了。

Android的成功依赖于你的程序提供的用户体验。而这种用户体验,部分依赖于你的程序是响应快速而灵活的,还是响应缓慢而僵化的。因为所有的程序都运行在同一个设备之上,都在一起,这就如果在同一条路上行驶的汽车。而这篇文档就相当于你在取得驾照之前必须要学习的交通规则。如果大家都按照这些规则去做,驾驶就会很顺畅,但是如果你不这样做,你可能会车毁人亡。这就是为什么这些原则十分重要。

当我们开门见山、直击主题之前,还必须要提醒大家一点:不管VM是否支持实时(JIT)编译器(xing:它允许实时地将Java解释型程序自动编译成本机机器语言,以使程序执行的速度更快。有些JVM包含JIT编译器。),下面提到的这些原则都是成立的。假如我们有目标完全相同的两个方法,在解释执行时foo()比bar()快,那么编译之后,foo()依然会比bar()快。所以不要寄希望于编译器可以拯救你的程序。

避免建立对象



世界上没有免费的对象。虽然GC为每个线程都建立了临时对象池,可以使创建对象的代价变得小一些,但是分配内存永远都比不分配内存的代价大。

如果你在用户界面循环中分配对象内存,就会引发周期性的垃圾回收,用户就会觉得界面像打嗝一样一顿一顿的。

所以,除非必要,应尽量避免尽力对象的实例。下面的例子将帮助你理解这条原则:

当你从用户输入的数据中截取一段字符串时,尽量使用substring函数取得原始数据的一个子串,而不是为子串另外建立一份拷贝。这样你就有一个新的String对象,它与原始数据共享一个char数组。

如果你有一个函数返回一个String对象,而你确切的知道这个字符串会被附加到一个StringBuffer,那么,请改变这个函数的参数和实现方式,直接把结果附加到StringBuffer中,而不要再建立一个短命的临时对象。

一个更极端的例子是,把多维数组分成多个一维数组。

int数组比Integer数组好,这也概括了一个基本事实,两个平行的int数组比(int,int)对象数组性能要好很多。同理,这试用于所有基本类型的组合。

如果你想用一种容器存储(Foo,Bar)元组,尝试使用两个单独的Foo[]数组和Bar[]数组,一定比(Foo,Bar)数组效率更高。(也有例外的情况,就是当你建立一个API,让别人调用它的时候。这时候你要注重对API借口的设计而牺牲一点儿速度。当然在API的内部,你仍要尽可能的提高代码的效率)

总体来说,就是避免创建短命的临时对象。减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。

使用本地方法



当你在处理字串的时候,不要吝惜使用String.indexOf(), String.lastIndexOf()等特殊实现的方法(specialty methods)。这些方法都是使用C/C++实现的,比起Java循环快10到100倍。

使用实类比接口好

假设你有一个HashMap对象,你可以将它声明为HashMap或者Map:

Map myMap1 = new HashMap();HashMapmyMap2 = new HashMap();



哪个更好呢?

按照传统的观点Map会更好些,因为这样你可以改变他的具体实现类,只要这个类继承自Map接口。传统的观点对于传统的程序是正确的,但是它并不适合嵌入式系统。调用一个接口的引用会比调用实体类的引用多花费一倍的时间。

如果HashMap完全适合你的程序,那么使用Map就没有什么价值。如果有些地方你不能确定,先避免使用Map,剩下的交给IDE提供的重构功能好了。(当然公共API是一个例外:一个好的API常常会牺牲一些性能)

用静态方法比虚方法好

如果你不需要访问一个对象的成员变量,那么请把方法声明成static。虚方法执行的更快,因为它可以被直接调用而不需要一个虚函数表。另外你也可以通过声明体现出这个函数的调用不会改变对象的状态。

不用getter和setter



在很多本地语言如C++中,都会使用getter(比如:i = getCount())来避免直接访问成员变量(i = mCount)。在C++中这是一个非常好的习惯,因为编译器能够内联访问,如果你需要约束或调试变量,你可以在任何时候添加代码。

在Android上,这就不是个好主意了。虚方法的开销比直接访问成员变量大得多。在通用的接口定义中,可以依照OO的方式定义getters和setters,但是在一般的类中,你应该直接访问变量。

将成员变量缓存到本地

访问成员变量比访问本地变量慢得多,下面一段代码:

for (int i = 0; i < this.mCount;i++)dumpItem(this.mItems);



再好改成这样:

int count = this.mCount;Item[] items= this.mItems; for (int i = 0; i < count; i++)dumpItems(items);

(使用"this"是为了表明这些是成员变量)

另一个相似的原则是:永远不要在for的第二个条件中调用任何方法。如下面方法所示,在每次循环的时候都会调用getCount()方法,这样做比你在一个int先把结果保存起来开销大很多。

for (int i = 0; i <this.getCount(); i++)dumpItems(this.getItem(i));

同样如果你要多次访问一个变量,也最好先为它建立一个本地变量,例如:

protected voiddrawHorizontalScrollBar(Canvas canvas, int width, intheight) {if(isHorizontalScrollBarEnabled()) {int size =mScrollBar.getSize(false);if (size<= 0) {size =mScrollBarSize;}mScrollBar.setBounds(0, height - size,width,height);mScrollBar.setParams(computeHorizontalScrollRange(),computeHorizontalScrollOffset(),computeHorizontalScrollExtent(),false);mScrollBar.draw(canvas);}}

这里有4次访问成员变量mScrollBar,如果将它缓存到本地,4次成员变量访问就会变成4次效率更高的栈变量访问。

另外就是方法的参数与本地变量的效率相同。



使用常量

让我们来看看这两段在类前面的声明:

static int intVal = 42;static StringstrVal = "Hello, world!";

必以其会生成一个叫做<clinit>的初始化类的方法,当类第一次被使用的时候这个方法会被执行。方法会将42赋给intVal,然后把一个指向类中常量表的引用赋给strVal。当以后要用到这些值的时候,会在成员变量表中查找到他们。下面我们做些改进,使用“final"关键字:


static final int intVal = 42;staticfinal String strVal = "Hello, world!";

现在,类不再需要<clinit>方法,因为在成员变量初始化的时候,会将常量直接保存到类文件中。用到intVal的代码被直接替换成42,而使用strVal的会指向一个字符串常量,而不是使用成员变量。

将一个方法或类声明为"final"不会带来性能的提升,但是会帮助编译器优化代码。举例说,如果编译器知道一个"getter"方法不会被重载,那么编译器会对其采用内联调用。

你也可以将本地变量声明为"final",同样,这也不会带来性能的提升。使用"final"只能使本地变量看起来更清晰些(但是也有些时候这是必须的,比如在使用匿名内部类的时候)(xing:原文是 or youhave to, e.g. for use in an anonymousinner class)



谨慎使用foreach

foreach可以用在实现了Iterable接口的集合类型上。foreach会给这些对象分配一个iterator,然后调用hasNext()和next()方法。你最好使用foreach处理ArrayList对象,但是对其他集合对象,foreach相当于使用iterator。

下面展示了foreach一种可接受的用法:

public class Foo {int mSplat;staticFoo mArray[] = new Foo[27]; publicstatic void zero() {int sum = 0;for (int i =0; i < mArray.length;i++) {sum += mArray.mSplat;}} public static void one(){int sum =0;Foo[] localArray = mArray;int len = localArray.length;for (int i =0;i < len; i++) {sum += localArray.mSplat;}} public static voidtwo() {intsum = 0;for (Foo a: mArray) {sum += a.mSplat;}}}

在zero()中,每次循环都会访问两次静态成员变量,取得一次数组的长度。 retrieves the static field twiceand gets the array length once forevery iteration through the loop.

在one()中,将所有成员变量存储到本地变量。 pulls everything out into local variables, avoiding the lookups.

two()使用了在java1.5中引入的foreach语法。编译器会将对数组的引用和数组的长度保存到本地变量中,这对访问数组元素非常好。但是编译器还会在每次循环中产生一个额外的对本地变量的存储操作(对变量a的存取)这样会比one()多出4个字节,速度要稍微慢一些。

综上所述:foreach语法在运用于array时性能很好,但是运用于其他集合对象时要小心,因为它会产生额外的对象。



避免使用枚举

枚举变量非常方便,但不幸的是它会牺牲执行的速度和并大幅增加文件体积。例如:

public class Foo {public enumShrubbery { GROUND, CRAWLING, HANGING }}

会产生一个900字节的.class文件(Foo$Shubbery.class)。在它被首次调用时,这个类会调用初始化方法来准备每个枚举变量。每个枚举项都会被声明成一个静态变量,并被赋值。然后将这些静态变量放在一个名为"$VALUES"的静态数组变量中。而这么一大堆代码,仅仅是为了使用三个整数。



这样:

Shrubbery shrub = Shrubbery.GROUND;会引起一个对静态变量的引用,如果这个静态变量是final int,那么编译器会直接内联这个常数。

一方面说,使用枚举变量可以让你的API更出色,并能提供编译时的检查。所以在通常的时候你毫无疑问应该为公共API选择枚举变量。但是当性能方面有所限制的时候,你就应该避免这种做法了。

有些情况下,使用ordinal()方法获取枚举变量的整数值会更好一些,举例来说,将:

for (int n = 0; n < list.size();n++) {if (list.items[n].e ==MyEnum.VAL_X)// do stuff 1else if (list.items[n].e== MyEnum.VAL_Y)//do stuff 2}

替换为:

int valX = MyEnum.VAL_X.ordinal();intvalY = MyEnum.VAL_Y.ordinal();intcount = list.size();MyItem items =list.items(); for (int n = 0; n <count; n++){int valItem =items[n].e.ordinal(); if (valItem == valX)//do stuff 1else if (valItem ==valY)// do stuff 2}

会使性能得到一些改善,但这并不是最终的解决之道。

将与内部类一同使用的变量声明在包范围内



请看下面的类定义:

public class Foo {private int mValue;public void run() {Inner in = newInner();mValue = 27;in.stuff();} private voiddoStuff(int value){System.out.println("Value is " + value);} privateclass Inner {voidstuff() {Foo.this.doStuff(Foo.this.mValue);}}}

这其中的关键是,我们定义了一个内部类(Foo$Inner),它需要访问外部类的私有域变量和函数。这是合法的,并且会打印出我们希望的结果"Value is 27"。

问题是在技术上来讲(在幕后)Foo$Inner是一个完全独立的类,它要直接访问Foo的私有成员是非法的。要跨越这个鸿沟,编译器需要生成一组方法:

static int Foo.access$100(Foo foo){return foo.mValue;}static void Foo.access$200(Foo foo, int value) {foo.doStuff(value);}

内部类在每次访问"mValue"和"doStuff"方法时,都会调用这些静态方法。就是说,上面的代码说明了一个问题,你是在通过接口方法访问这些成员变量和函数而不是直接调用它们。在前面我们已经说过,使用接口方法(getter、setter)比直接访问速度要慢。所以这个例子就是在特定语法下面产生的一个“隐性的”性能障碍。

通过将内部类访问的变量和函数声明由私有范围改为包范围,我们可以避免这个问题。这样做可以让代码运行更快,并且避免产生额外的静态方法。(遗憾的是,这些域和方法可以被同一个包内的其他类直接访问,这与经典的OO原则相违背。因此当你设计公共API的时候应该谨慎使用这条优化原则)



避免使用浮点数

在奔腾CPU出现之前,游戏设计者做得最多的就是整数运算。随着奔腾的到来,浮点运算处理器成为了CPU内置的特性,浮点和整数配合使用,能够让你的游戏运行得更顺畅。通常在桌面电脑上,你可以随意的使用浮点运算。

但是非常遗憾,嵌入式处理器通常没有支持浮点运算的硬件,所有对"float"和"double"的运算都是通过软件实现的。一些基本的浮点运算,甚至需要毫秒级的时间才能完成。

甚至是整数,一些芯片有对乘法的硬件支持而缺少对除法的支持。这种情况下,整数的除法和取模运算也是有软件来完成的。所以当你在使用哈希表或者做大量数学运算时一定要小心谨慎。

 




阅读(3292) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~