Android 性能优化—开机动画优化

Android 手机的分辨率越来越大,从矮穷锉到百富美都是如此.现在矮穷锉也是HD的屏而百富美一般是2K, 8CPU, 3GRAM.然并卵,我们依然会发现大多数的开机动画并不是很流畅,而比较流畅的,大都要么画面简单有的只是showlogo, 要么降低动画的分辨率/FPS.比如本来是百富美的HW(2K),却用矮穷锉的开机动画(HD/10fps开机动画).当然,这里有androiddesign 的问题,也有手机厂商的设置animation内容的问题.本文将提供如何在矮穷锉HW上实现真正百富美HW才有的功能(FHD,或者2K 30FPS).

1.1 Bootanimation架构

android ,开机动画并不仅仅是开机动画,而还包含开机铃声.开机动画都压缩在bootanimation.zip,而开机铃声L以及以前的版本中可以指定某一目录下的audio 文件也可以把开机铃声放到bootanimation.zip就只能放到bootanimation.zip中除非修改androidcode.本文只讨论动画.事实上所谓的开机动画其实就是几十张或上百张的PNG或者JPG 图片按照一定的顺序, FPS播放出来所有的开机动画都压缩在bootanimation.zip,其位置是/system/media/bootanimation.zip. 现在我们打开bootanimation.zip来看看究竟有些什么内容.

+bootanimation.zip

| | +part0

| |480_854_00000.png

| |480_854_00001.png

| |480_854_00002.png

| |480_854_00003.png

| |480_854_00004.png

| |480_854_00005.png

|+------------------

| +part1

| | 480_854_00000.png

| |480_854_00001.png

| |480_854_00002.png

| |480_854_00003.png

| |480_854_00004.png

| |480_854_00005.png

……

|+------------------

| +partn

| | 480_854_00000.png

| |480_854_00001.png

| |480_854_00002.png

| |480_854_00003.png

| |480_854_00004.png

| |480_854_00005.png

|+------------------

| | audio.wav

|+------------------

| desc.txt

+--------------------

|+------------------

| audio_conf.txt

+--------------------

这里我们可以看到在bootanimation.zip中由part1,part2,part3 … partn, audio.wav

, desc.txtaudio_conf.txt 构成.其中audio.wavaudio_conf.txt是和开机动画的audio相关我们在本文略去不表.partdesc.txt与开机动画有关.每一个part 又由一系列的pngjpg格式的图片按照数字或字母的大小顺序排列而成. desc.txt则是描述怎样处理这些图片.

desc.txt

#typicaldesc.txt

480854 10

p1 0 part0

p0 0 part1

这里是典型的矮穷锉的配置.但是百富美的架构也和这个相同只是内容不同而已

“480” 
表示这些图片的宽度是480.
“854” 表示这些图片的高度是854.
“10” 表示开机动画的目标fps 10. 

“p” 
这个flag表示某一个part的图片的处理方式,bootanimation 程序在处理某part的图片时会参考这个标志.P表示bootanimation可以随时退出当处理完一张图片后,如果bootanimation可以退出. ”C” 则表示必须把这部分的图片处理完毕才可以退出.事实上这个标识只要不为C, 则其行为完全一致.

“1”这个flag表示这part的图片需要循环处理多少次. 1表示处理循环处理一次.”0” 表示无限循环.
“0” 
这个flag 表示播放完这部分所有图片后需要pause多久时间.”0” 表示不需要pause. “part0”表示动画的第一部分内容.
“part1” 表示动画的第二部分内容.

“partn” 表示动画的第n+1部分内容.

这里我们讨论bootanimation程序的退出时机,正如前面讨论的,bootanimation程序会根据part 描述的第一个标识来处理怎样退出bootanimation.我们先假定此时bootanimation满足退出条件(propservice.bootanim.exit 1) bootanimation 在三种条件下可以退出.

  • 开始处理part的新循环或新part. 如果这部分part的处理标识不是”c”则可以退出.否则不能退出.

  • 开始处理新的一帧如果这部分part的处理标识不是”c”则可以退出.否则不能退出.

  • 处理完一次循环后如果这部分part的处理标识是”c”,并且这部分是无限循环,则可以退出.如果不是无限循环则不能退出需要完全处理完这部分的图片才可能退出.

总之如果我们设置part的处理标识设置为”c”,则需要好好考虑和组织我们animation的图片.一步小心可能会把百富美的HW的性能搞成矮穷锉HW的性能.(开机时间过长).不建议设置此标识位为”C”.

frameworks\base\cmds\bootanimation\bootAnimation.cpp

bool BootAnimation::movie()

{

….

elseif (sscanf(l, " %c %d %d %s #%6s", &pathType, &count,&pause, path, color) >= 4)

Animation::Part part;

part.playUntilComplete = pathType == 'c';

}

….

for(int r=0 ; !part.count || r

// Exitany non playuntil complete parts immediately

if(exitPending() &&!part.playUntilComplete) //check exit condition 1

break;

….

for(size_t j=0 ; j(!exitPending() || part.playUntilComplete) ; j++) //check exit condition 2

{

……

}

// For infinite parts, we've now played them at least once,so perhaps exit

if(exitPending()&& !part.count) //check exit condition 3

break;

}

我们知道boot animation在开机中占有重要地位,它也会影响开机时间.下面我们来讨论开机动画的工作流程.

1.2 开机动画的流程

在开机动画的生命周期中,至少有5个不同的进程参与其中.他们分别是:init ,surfaceflinger, system server,bootanimation Lancher.

Android 性能优化—开机动画优化_第1张图片

  1. Kernel将会启动init 进程当kernel启动完成后.

  2. Init进程将会启动那些定义在init.rc 中的进程.zygote,surfaceflinger.

  3. Sufacefinger进程会启动bootanimation 进程当surfaceflinger 进程启动时.(SurfaceFlinger::startBootAnim()).

  4. Bootanimation进程处理开机动画.同时zygote 进程启动systemserver 进程. AMS ,WMS 将会在systemserver 进程里启动,进而启动launcher 进程.

  5. Launcher进程的UI线程将会call AMSactivityIdle() Launcher 进程完成了启动,并且没有其它event 需要处理.注意这是Android framwork的功能.每一个app启动完成后,在其主线程有空闲的时候(没有需要处理的event)都会call AMSactivityIdle().这个idle handler 是在handleResumeActivity中注册的.所以当app 启动完成后都会call AMSactivityIdle(). AMSactivityIdle()中会调用WindowManagerService. performEnableScreen() call.

    frameworks\base\services\core\java\com\android\server\am\ActivityThread.java

    final voidhandleResumeActivity(IBinder token,

    boolean clearHide, booleanisForward, boolean reallyResume)

    {

    ….

    f(!r.onlyLocalRequest) {

    r.nextIdle = mNewActivities;

    mNewActivities = r;

    if (localLOGV) Slog.v(

    TAG, "Scheduling idlehandler for " + r);

    Looper.myQueue().addIdleHandler(new Idler());

    }

    }

    Idler 定义.

    private classIdler implements MessageQueue.IdleHandler {

    @Override

    public final boolean queueIdle() {

    ActivityClientRecord a =mNewActivities;

    boolean stopProfiling = false;

    if (mBoundApplication != null&& mProfiler.profileFd != null

    &&mProfiler.autoStopProfiler) {

    stopProfiling = true;

    }

    if (a != null) {

    mNewActivities = null;

    IActivityManager am =ActivityManagerNative.getDefault();

    ActivityClientRecord prev;

    do {

    if (localLOGV) Slog.v(

    TAG, "Reportingidle of " + a +

    " finished=" +

    (a.activity != null&& a.activity.mFinished));

    if (a.activity != null&& !a.activity.mFinished) {

    try {

    am.activityIdle(a.token,a.createdConfig, stopProfiling); //这里会callAMS. activityIdle by binder

    a.createdConfig =null;

    } catch(RemoteException ex) {

    // Ignore

    }

    }

    prev = a;

    a = a.nextIdle;

    prev.nextIdle = null;

    } while (a != null);

    }

    if (stopProfiling) {

    mProfiler.stopProfiling();

    }

    ensureJitEnabled();

    return false;

    }

    }

  6. WindowManagerService.performEnableScreen()将通过发送IBinder.FIRST_CALL_TRANSACTION.从而callsurfaceFlinger. bootFinished().如果比较顺利的话.注意这里也是很容易由白富美变为矮穷锉如果KeyguardWallpaper处理得不好的话.开机动画和开机时间会边长.

    frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java

    public voidperformEnableScreen() {

    synchronized(mWindowMap) {

    ……

    // Don't enable the screen untilall existing windows have been drawn.

    //这里会check KeyguardWallpaper.

    if (!mForceDisplayEnabled &&checkWaitingForWindowsLocked()) {

    return;

    }

    if (!mBootAnimationStopped) {

    // Do this one time.

    try {

    IBinder surfaceFlinger =ServiceManager.getService("SurfaceFlinger");

    if (surfaceFlinger != null){

    //Slog.i(TAG,"******* TELLING SURFACE FLINGER WE ARE BOOTED!");

    Parcel data = Parcel.obtain();

    data.writeInterfaceToken("android.ui.ISurfaceComposer");

    surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION,// BOOT_FINISHED

    data, null, 0);

    data.recycle();

    }

    } catch (RemoteException ex) {

    Slog.e(TAG, "Bootcompleted: SurfaceFlinger is dead!");

    }

    mBootAnimationStopped = true;

    }

    ….

    }

    private booleancheckWaitingForWindowsLocked(){

    …..

    final WindowList windows =getDefaultWindowListLocked();

    final int N = windows.size();

    for (int i=0; i

    WindowState w = windows.get(i);

    if (w.isVisibleLw() &&!w.mObscured && !w.isDrawnLw()) {

    return true;

    }

    if (w.isDrawnLw()) {

    if (w.mAttrs.type ==TYPE_BOOT_PROGRESS) {

    haveBootMsg = true;

    } else if (w.mAttrs.type ==TYPE_APPLICATION) {

    haveApp = true;

    } else if (w.mAttrs.type ==TYPE_WALLPAPER) {

    haveWallpaper = true;

    } else if (w.mAttrs.type ==TYPE_STATUS_BAR) {

    haveKeyguard =mPolicy.isKeyguardDrawnLw();

    }

    }

    }

    // If we are turning on the screen toshow the boot message,

    // don't do it until the boot messageis actually displayed.

    if (!mSystemBooted &&!haveBootMsg) {

    return true;

    }

    ….

    // If we are turning on the screenafter the boot is completed

    // normally, don't do so until we havethe application and

    // wallpaper.

    if (mSystemBooted && ((!haveApp&& !haveKeyguard) ||

    (wallpaperEnabled &&!haveWallpaper))) {

    return true;

    }

    return false;

    }

  7. 如前所述Bootanimation 会在关键节点去check 是否需要exit. 当且仅当属性“service.bootanim.exit“ , bootanimation 会退出.

1.3 矮穷锉逆袭百富美

前面我们分析了开机动画的组织架构以及工作流程现在就到了最重要的时刻了.我们来看看矮穷锉如何逆袭百富美.那么哪些是矮穷锉的行为典型的行为有如下两点:

  • Kernel logo 的时间太长 (10s以上), 而开机动画的时间太短.

    如果出现这种现象,给人的第一印象便是这台手机的CPUTM 矮穷锉了我们来看看问题到底在哪.我们知道android 原来的开机动画design 为只有开机动画而没有开机铃声或者开机铃声不在bootanimation里处理所以其bootanimation 进程的user 定义为Graphic ,随着消费者要求的提高就出现了需要在bootanimation进程中处理audio,这样就需要修改bootanimation进程的用户为media. 因为auido service 只能media root 用户能访问.

    那么问题到底出现在哪里?我们来看看bootanimation 进程的定义大多数手机的定义如下:

    service bootanim/system/bin/bootanimation

    class core

    user media

    group graphics audio

    disabled

    oneshot

    user media.我们接着看其Bootanimation构造函数的定义.

  1. Bootanimationnew SurfaceComposerClient().

frameworks\base\cmds\bootanimation\bootAnimation.cpp

BootAnimation::BootAnimation(): Thread(false), mZip(NULL)

{

mSession = new SurfaceComposerClient();

}

b.SurfaceComposerClient的定义如下:

frameworks\native\libs\gui\SurfaceComposerClient.cpp

SurfaceComposerClient::SurfaceComposerClient()

: mStatus(NO_INIT),mComposer(Composer::getInstance())

{

}

c.在onFirstRef()会去连接surfacefinger.

voidSurfaceComposerClient::onFirstRef() {

spsm(ComposerService::getComposerService());

if (sm != 0) {

sp conn =sm->createConnection();

if (conn != 0) {

mClient = conn;

mStatus = NO_ERROR;

}

}

}

d.连接surfaceflinger,check permission.现在**部分来了

frameworks\native\services\surfaceflinger\ SurfaceFlinger.cpp

status_tSurfaceFlinger::onTransact(

uint32_t code, constParcel& data, Parcel* reply, uint32_t flags)

{

switch (code) {

case CREATE_CONNECTION:

case CREATE_DISPLAY:

case SET_TRANSACTION_STATE:

case BOOT_FINISHED:

case CLEAR_ANIMATION_FRAME_STATS:

case GET_ANIMATION_FRAME_STATS:

case SET_POWER_MODE:

{

// codes that require permissioncheck

IPCThreadState* ipc =IPCThreadState::self();

const int pid =ipc->getCallingPid();

const int uid = ipc->getCallingUid();

if ((uid != AID_GRAPHICS && uid != AID_SYSTEM) &&

!PermissionCache::checkPermission(sAccessSurfaceFlinger, pid, uid)){

ALOGE("Permission Denial:"

"can't accessSurfaceFlinger pid=%d, uid=%d", pid, uid);

return PERMISSION_DENIED;

}

break;

…..

}

}

e.我们从上面的代码中看到在surfaceflinger的很多操作例如连接,创建display和设置power 等都需要检查permission。除非进程的user graphics system。然而几乎在android的所有版本中,media 用户都是有ACCESS_SURFACE_FLINGER permission的。其定义在平台的permission文件中。

frameworks\base\data\etc\platform.xml

另一方面,permissioncheck是在javaservice permission service。当bootanimation开始运行的时候permission 还没有看是运行。Permisison service运行起来还需一段时间(89s)。因此开机动画在这里被阻塞了。其结果就是百富美变成了矮穷锉。如果我们要从矮穷锉完美的逆袭为百富美,只需做如下改动即可。我们为什么我们不把bootanimation 改为root 用户,我想你懂的。

if ((uid !=AID_GRAPHICS && uid != AID_SYSTEM) &&

!PermissionCache::checkPermission(sAccessSurfaceFlinger, pid, uid))

if ((uid != AID_GRAPHICS && uid != AID_SYSTEM&&uid != AID_MEDIA) &&

!PermissionCache::checkPermission(sAccessSurfaceFlinger,pid, uid))

  • 开机动画fps 太低,不流程。

    我们知道很多开机动画要么是手机厂商logo或者手机的宣传片,要么是运行商的宣传片。可以说是手机的脸面,在现如今的刷脸时代,没有一个好的面孔是很难找到高大帅的。那么是什么原因导致矮穷锉满天下。我们还是看看code吧。

    frameworks\base\cmds\bootanimation\bootAnimation.cpp

    boolBootAnimation::movie()

    {

    ….

    for (size_t i=0 ; i

    for (int r=0 ; !part.count ||r

    …..

    for (size_t j=0 ; j

    ….

    initTexture(frame);

    }

    return false;

    }

    status_t BootAnimation::initTexture(Texture* texture, AssetManager&assets,

    const char* name) {

    SkBitmap bitmap;

    SkImageDecoder::DecodeMemory(asset->getBuffer(false),asset->getLength(),

    &bitmap, kUnknown_SkColorType,SkImageDecoder::kDecodePixels_Mode);

    asset->close();

    delete asset;

    ….

    glTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_CROP_RECT_OES, crop);

    glTexParameterx(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    glTexParameterx(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    glTexParameterx(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_REPEAT);

    glTexParameterx(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_REPEAT);

    return NO_ERROR;

    }

    看出点什么名堂出来没。好像没有什么问题吧?是的,功能上是没有什么问题。那么从性能上看是否有问题的?对的,这里用的是SW decoder SkImageDecoder::DecodeMemory(),而且是单线程那么其解码效率如何?如果你巧合用了百富美平台,又巧合的对SW decoder进行了优化如neon指令做了优化,那么恭喜你。你已经没有此问题的烦恼。对此问题如何解决呢?聪明的你一定想到两种方案。一种是比较low的方案,继续往矮穷锉的方向发展,那就是降低开机动画的分辨率来提高解码速度从而提高开机动画的fps。另一种就是向百富美看其,用多线程来进行解码,把解码好的数据放到一个buffer queue中,需要用到解码的frame时直接从bufferqueue里拿而不是解一个frame,画一个frame。充分利用多核的计算能力。

Android 性能优化—开机动画优化_第2张图片

你可能感兴趣的:(android)