现在的位置: 首页 > 综合 > 正文

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

2018年08月24日 ⁄ 综合 ⁄ 共 26955字 ⁄ 字号 评论关闭

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

bootloader          

       引导程序

kernel        

      内核

init

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

loads several daemons and services, including zygote
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 
参考http://elinux.org/Grabserial

printk times

 参考http://elinux.org/Printk_Times

logcat 
Android自带

bootchart 

参考http://elinux.org/Bootchart 和       http://elinux.org/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 --git a/core/java/com/android/internal/os/ZygoteInit.java 

             b/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 class ZygoteInit {
                         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,
                                     " GC at " + 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 and writes over compiled log file.

*/

public class WritePreloadedClassFile

    /**
     * Preload any class that take longer to load than MIN_LOAD_TIME_MICROS us.
     */

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一次以上的。

上面的都是指的system class,另外还有一些application class需要被装载规则是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 to the 
     * 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,

                        new AccountManagerService(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 = new StatusBarService(context);
                ServiceManager.addService("statusbar", statusBar);
       
                ServiceManager.addService("clipboard", new ClipboardService(context));
       
                imm = new InputMethodManagerService(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,
                      new AccessibilityManagerService(context));
        
                notification = new NotificationManagerService(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, new LocationManagerService(context));
      
                ServiceManager.addService( Context.SEARCH_SERVICE, new SearchManagerService(context) );

            if (INCLUDE_DEMO) {
                Log.i(TAG, "Installing demo data...");
                (new DemoThread(context)).start();
            }
                Intent intent = new Intent().setComponent(new ComponentName(
                        "com.google.android.server.checkin",
                        "com.google.android.server.checkin.CheckinService"));        
                    ServiceManager.addService("checkin", new FallbackCheckinService(context));

                wallpaper = new WallpaperManagerService(context);
                ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);     
     
                ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context));       
                headset = new HeadsetObserver(context);
    
                dock = new DockObserver(context, power);   
                ServiceManager.addService(Context.BACKUP_SERVICE, new BackupManagerService(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 its contents 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 unsigned char*)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 archive doesn'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 it immediately 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 a minimum 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
}

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

posted @ 2012-06-13 14:32 wanqi Views(246) Comments(0) Edit
Android Handler详解
导读:首先创建一个Handler对象,可以直接使用Handler无参构造函数创建Handler对象,也可以继承Handler类,重写handleMessage方法来创建Handler对象。
  1、首先创建一个Handler对象,可以直接使用Handler无参构造函数创建Handler对象,也可以继承Handler类,重写handleMessage方法来创建Handler对象。

  2、在监听器中,调用Handler的post方法,将要执行的线程对象添加到线程队列当中。此时将会把该线程对象添加到handler对象的线程队列中。

  3、将要执行的操作写在线程对象的run方法中,一般是一个Runnable对象,复写其中的run方法就可以了。
  Handler包含了两个队列,其中一个是线程队列,另外一个是消息队列。使用post方法会将线程对象放到该handler的线程队列中,使用sendMessage(Message message)将消息放到消息队列中。

  如果想要这个流程一直执行的话,可以在run方法内部执行postDelayed或者post方法,再将该线程对象添加到消息队列中,重复执行。想要线程停止执行,调用Handler对象的removeCallbacks(Runnable r) 方法从线程队列中移除线程对象,使线程停止执行。
  Handler为Android提供了一种异步消息处理机制,当向消息队列中发送消息 (sendMessage)后就立即返回,而从消息队列中读取消息时会阻塞,其中从消息队列中读取消息时会执行Handler中的public void handleMessage(Message msg) 方法,因此在创建Handler时应该使用匿名内部类重写该方法,在该方法中写上读取到消息后的操作,使用Handler的 obtainMessage() 来获得消息对象。

  Handler与线程的关系:
  使用Handler的post方法将Runnable对象放到Handler的线程队列中后,该Runnable的执行其实并未单独开启线程,而是仍然在当前Activity线程中执行的,Handler只是调用了Runnable对象的run方法。

  Bundle是什么:
  Bundle是一个特殊的map,它是传递信息的工具,它的键只能是string类型,而且值也只能是常见的基本数据类型。

  如何让Handler执行Runnable时打开新的线程:
  1、首先生成一个HandlerThread对象,实现了使用Looper来处理消息队列的功能,这个类由Android应用程序框架提供
  HandlerThread handlerThread = new HandlerThread("handler_thread");
  2、在使用HandlerThread的getLooper()方法之前,必须先调用该类的start();
  handlerThread。start();
  3、根据这个HandlerThread对象得到其中的Looper对象。
  4、创建自定义的继承于Handler类的子类,其中实现一个参数为Looper对象的构造方法,方法内容调用父类的构造函数即可。
  5、使用第三步得到的Looper对象创建自定义的Handler子类的对象,再将消息(Message)发送到该Handler的消息队列中,Handler复写的handleMessage()将会执行来处理消息队列中的消息。
  消息,即Message对象,可以传递一些信息,可以使用arg1。arg2,Object传递一些整形或者对象,还可以使用Message对象的 setData(Bundle bundle)来讲Bundle对象传递给新创建的线程,新创建的线程在执行handleMessage(Message msg)时可以从message中利用getData()提取出Bundle对象来进行处理。
  Java代码:
public class HandlerTest2 extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
stub
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//打印了当前线程的ID
System.out.println("Activity-->" + Thread.currentThread().getId());
//生成一个HandlerThread对象,实现了使用Looper来处理消息队列的功能,这个类由Android应用程序框架提供
HandlerThread handlerThread = new HandlerThread("handler_thread"); 11 //在使用HandlerThread的getLooper()方法之前,必须先调用该类的start();
handlerThread.start();
MyHandler myHandler = new MyHandler(handlerThread.getLooper());
Message msg = myHandler.obtainMessage();
//将msg发送到目标对象,所谓的目标对象,就是生成该msg对象的handler对象
Bundle b = new Bundle(); 17 b.putInt("age", 20);
b.putString("name", "Jhon");
msg.setData(b);
msg.sendToTarget();
}
class MyHandler extends Handler{
public MyHandler(){
}
public MyHandler(Looper looper){
super(looper); 29 }
@Override
public void handleMessage(Message msg) {
Bundle b = msg.getData();
int age = b.getInt("age");
String name = b.getString("name");
System.out.println("age is " + age + ", name is" + name);
System.out.println("Handler--->" + Thread.currentThread().getId());
System.out.println("handlerMessage");
}
}
}
 
posted @ 2012-06-13 14:18 wanqi Views(706) Comments(0) Edit
Android Mediaplayer解读 Android Mediaplayer解读 

http://blog.csdn.net/menguio/article/details/6323965

1 Gallery应用端表现
    Gallery仅仅提供一个呈现框架,Gallery用来管理所有的视频和图片文件,具有播放、查看、删除等功能。自动搜索本地sdcard存有的picture和video,并分类将同性质文件picture和video集中在一起,播放时呈现。Gallery内部实现的播放主用是同MediaPlayer,主要包含了Audio和video的播放功能。
    
    Gallery中增加从指定目录选择播放文件的功能:
方法:首先遍历sdcard下的目录,然后通过选择某个目录,再遍历文件,点击文件播放。
说明:
    定义了两个List表:
    videoPathList:遍历/sdcard下目录,并存于此List。
    videlFileList:遍历相应目录下的文件,并存于此List。
    定义了两个Activity:
    VideoList2Play:实现/sdcard下目录和文件遍历。
    VideoPlayer:利用VideoView类实现视频播放。
    从Gallery的按钮和菜单项着手,将该功能植入Gallery。
    Gallery首次加载的图框上“拍照按钮”,能够找到内部功能实现的类和方法,并可以将该功能加入。
    利用more菜单,增加“选择其它视频”功能,涉及内部类复杂,需进一步研究,暂没有实现。
    利用视频播放是的MovieView中增加“选择其它视频”按钮,当有视频真正在播放时,点击按钮选择其它视频,会有冲突。
需改进:研究通过弹出选择对话框方式,可以自由选择/sdcard下的目录和文件,并实现播放。

    Gallery中数据缓存及处理流程
    在应用程序中有三个线程存在:主线程(随activity的声明周期启动销毁)、feed初始化线程(进入程序时只运行一次,用于加载相册初始信息)、feed监听线程(监听相册和相片的变更)。主要流程归纳如下:
    1、首次进入程序Gallery调用onCreate,此时发送初始化消息进入消息队列;然后Gallery调用onResume,向下进入GridLayer的onResume。MediaFeed对象需要进行初始化,然后才可调用MediaFeed 的onResume;
    2、处理消息队列中的HANDLE_INTENT消息,Gallery处理这个消息会初始化数据源,从而调用GridLayer的setDataSource方法,这个方法会触发底层MediaFeed的启动方法start,执行完后启动feed监听线程继续执行MediaFeed的run方法。
    start方法会作两件事:调用自己底层的重新开始方法onResume,onResume中会为图像和视频这两个媒体源分别增加“内容变化监听器”,并请求刷新这两个媒体源(加入全局的刷新请求列表);启动feed初始化线程mAlbumSourceThread。
    3、其中MediaFeed初始化线程的工作是:调用MediaFeed的loadMediaSets加载相册,它又调用了下层 LocalDataSource中的refresh方法(查询数据库是否有相册变化,新增或修改feed中相应的MediaSet相册的名字)和 loadMediaSets方法(调用下层CacheService.loadMediaSets方法)加载所有相册和相册中的所有相片信息。
    4、MediaFeed监听线程MediaFeed.run()的工作是:根据“内容变化监听器”返回的媒体变动消息(增删改),持续不断的更新MediaFeed中的相册和相片变量。具体机制如下:如果全局的刷新请求列表中有内容,则调用LocalDataSource.refresh进行相册信息的更新(其中 LocalDataSource.refresh调用了CacheService的computeDirtySets),然后run遍历每个相册并调用dataSource.loadItemsForSet()方法为相册加载相片记录。

2 功能模块说明
1) 层次结构
    分为三层:上层Java应用程、中层Framework层、下层是底层libaries层。整个MediaPlayer在运行的时候,可以大致上分成Client和Server两个部分,它们分别在两个进程中运行,它们之间使用Binder机制实现IPC通讯。

2) 说明
  2.1) Gallery.java中通过Handler实现实现进程间通信,涉及sendInitialMessage()方法;检查存储的媒体文件,涉及checkStorage()方法;并初始化Data Source,涉及initializeDatasource()方法;判定数据类型后,通过onActivityResult()执行相应的活动。涉及到图层,GridLayer主图层,同GridDrawManager管理媒体的呈现。
  
 2.2) MediaPlayer的JAVA本地调用部分
    MediaPlayer的JAVA本地调用部分在目录frameworks/base/media/jni/的 android_media_MediaPlayer.cpp中的文件中实现。android.media.MediaPlayer中有2部分,一部分供java上层如VideoView调用,一部分为native方法,调用jni。
    通过MediaPlayerService实现client端和MediaPlayer进行交互和数据通信,其中涉及通过MediaProvider(多媒体内容提供者)调用数据源,MediaScannerService(多媒体扫描服务)和MediaScannerReceiver检查数据类型(这两个类之间通过Server和BroadcasterRevceiver, 主要的方法scan()、scanFlle()),并将统一类型的文件归类用MediaStore(多媒体存储)进行数据存储;MediaPlayer.java调用jni_android_media_MediaPlayer.jni进行同MediaPalyer.cpp实现通信。
       
    说明:
• MediaStore这个类是android系统提供的一个多媒体数据库,android中多媒体信息都可以从这里提取。这个MediaStore包括了多媒体数据库的所有信息,包括音频、视频和图像。
• MediaScannerReceiver在任何的ACTION_BOOT_COMPLETED, ACTION_MEDIA_MOUNTED或 ACTION_MEDIA_SCANNER_SCAN_FILE 意图(intent)发出的时候启动。因为解析媒体文件的元数据或许会需要很长时间,所以MediaScannerReceiver会启动MediaScannerService。
• MediaScannerService调用一个公用类MediaScanner进行媒体扫描工作。MediaScannerReceiver维持两种扫描目录:一种是内部卷(internal volume)指向$(ANDROID_ROOT)/media. 另一种是外部卷(external volume)指向$(EXTERNAL_STORAGE).

3) MediaPlayer
    Android的MediaPlayer包含了Audio和video的播放功能,在Android的界面上,Music和Video两个应用程序都是调用MediaPlayer实现的,上层还包含了进程间通讯等内容,这种进程间通讯的基础是Android基本库中的Binder机制。Android的媒体播放功能分成两部分,一部分是媒体播放应用,一部分是媒体播放服务。这两部分分别跑在不同的进程中。媒体播放应用包括Java程序和部分C++代码,媒体播放服务是C++代码。媒体播放应用和媒体播放服务之间需要通过binder机制来进行相互调用,这些调用包括:
   (1) 媒体播放应用向媒体播放服务发控制指令;
   (2) 媒体播放服务向媒体播放应用发事件通知(notify)。
•    媒体播放服务对外提供多个接口,其中有2个重要的接口:IMediaPlayerService和IMediaPlayer;IMediaPlayerServer用于创建和管理播放实例,而IMediaPlayer接口则是播放接口,用于实现指定媒体文件的播放以及播放过程的控制。
•        媒体播放应用向媒体播放服务提供的1个接口:IMediaPlayerClient,用于接收notify()。这些接口需要跨进程调用,涉及到binder机制(就是让这两部分之间建立联系)。每个接口包括两部分实现,一部分是接口功能的真正实现(BnInterface),这部分运行在接口提供进程中;另一部分是接口的proxy(BpInterface),这部分运行在调用接口的进程中。

3 代码框架
 
1) JAVA程序的路径:
packages/apps/Camera/
编译后生成Camera.apk,对应于Camera、Gallery、Camcorder三个应用。

packages/apps/Gallery/src/com/android/camera/gallery、
packages/apps/Gallery3D/src/com/cooliris/app

packages/providers/MediaProvider/
含有类MediaProvider.java、MediaScannerService.java、MediaScannerReceiver.java,
编译后生成MediaProvider.apk。会在开机时扫描本机和sdcard上的媒体文件(图片、视频、音频),并在/data/data/com.android.providers.media/databases 目录下生成internal.db(/system/meida)和external-?.db(/sdcard)两个数据库文件。此后所有的多媒体信息都从这两个数据库中获取。

2) JAVA Framework的路径:
frameworks/base/core/java/android/provider/MediaStore.java
提供的多媒体数据库,所有多媒体数据信息都可以从这里提取。数据库的操作通过利用ContentResolver调用相关的接口实现。

frameworks/base/media/java/android/media/
提供了android上 多媒体应用层的操作接口。主要说明:
• MediaPlayer.java:提供了视频、音频、数据流的播放控制等操作的接口。
• MediaScanner*.java:提供了媒体扫描接口的支持,媒体扫描后加入数据库中,涉及MediaScannerConnection.java和MediaScannerConnectionClient.java。

3) JAVA本地调用部分(JNI):
frameworks/base/media/jni
JAVA本地调用部分。编译后生成的目标是libmedia_jni.so。
•    android_media_MediaPlayer.cpp:JAVA本地调用部分,它定义了一个JNINativeMethod(JAVA本地调用方法)类型的数据gMethods用来描述接口的关联信息;定义了JNIMediaPlayerListener:MediaPlayerListener的notify()方法(该方法是调用c++层次的mediaplayer中,实现播放管制)。
•    android_media_MediaScanner.cpp: 媒体扫描相关的本地调用实现。处理路径、文件和Ablum相册内容释放。
•    soundpool/android_media_SoundPool.cpp:定义了音频系统的本地调用实现、MediaPlayer回调方法android_media_callback()。

4) 多媒体底层库:
frameworks/base/include/media/、frameworks/base/media/libmedia/
    这里为多媒体的的底层库,编译生成libmedia.so。这个库处于android多媒体架构的核心位置,它对上层提供的接口主要有MediaPlayer、MediaScanner等类。
    android.meida.* 就是通过libmedia_jni.so调用libmedia.so实现的接口实现的。    
    A) MediaPlayerInterface.h头文件定义了MediaPlayer的底层接口,定义了以下类:
•    MediaPlayerBase:MediaPlayerInterface的抽象基础类,里面包含了音频输出、视频输出、播放控制等的基本接口。
•        MediaPlayerInterface、MediaPlayerHWInterface 继承自MediaPlayerBase针对不同输出作出的扩展。
•        MediaPlayerInterface得到具有相同的播放接口,可以通过继承MediaPlayerInterface的方法,实现增加新的播放器实现。
    B) IMediaPlayer.h定义了BnMediaPlayer本地播放类;IMediaPlayer.cpp定义了BpMediaPlayer代理类(其中通过remote()->transact()方法发送消息)和实现了BnMediaPlayer:onTransact()的具体方法。
    C) IMediaPlayerClient.h定义了BnMediaPlayerClient本地客户端类;IMediaPlayerClient.cpp定义了BpMediaPlayerClient代理类(其中通过notify()中的remote()->transact()方法发送消息)和实现了BnMediaPlayerClient:onTransact()方法。
    D) IMediaPlayerService.h定义了BnMediaPlayerService本地服务端类;IMediaPlayerService.cpp定义了BpMediaPlayerService代理类(其中通过remote()->transact()方法发送消息)和实现了BnMediaPlayerService:onTransact()方法。
    E) mediaplayer.h定义了MediaPlayerListener类的notify()方法和类MediaPlayer:BnMediaPlayerClient;mediaplayer.cpp主要实现了MediaPlayer的数据设置播放和实现了MediaPlayerListener类的notify()具体方法。

5) 多媒体服务部分:
frameworks/base/media/libmediaplayerservice/
文件为mediaplayerservice.h和mediaplayerservice.cpp
    这是多媒体的服务部分(提供Media Player执行的Proxy,同Client端建立连接、设置数据源、根据不同类型创建播放),编译生成libmediaplayerservice.so。     
• MediaPlayerService.cpp 通过instantiate()方法实现了一个名字为media.player的服务,MediaPlayer通过IPC同其实现通讯;
• 根据playerType的类型来决定创建不同的播放器;
• 实现了notify()通知Client端、callbackThread()回调机制、decode解码。

frameworks/base/media/mediaserver/
文件为main_mediaserver.cpp是Mediaplayer Server启动的主程序,涉及AudioFlinger()、AudioPolicyService()、MediaPlayerService()的加载。

6) MediaPlayer生命周期:
 
4 Audio概念
Audio系统在Android中负责音频方面输入/输出和管理层次,一般负责播放PCM声音输出和从外部获取PCM声音,以及管理声音设备和设置。主要涉及到AudioManager、AudioTrack、AudioServiece、AudioRecord。主要分成如下几个层次:
 (1) media库提供的Audio系统本地部分接口;
 (2) AudioFlinger作为Audio系统的中间层;
 (3) Audio的硬件抽象层提供底层支持;
 (4) Audio接口通过JNI和Java框架提供给上层。
    Audio管理环节    Audio输出    Audio输入
Java层    android.media.
AudioSystem    android.media
AudioTrack    android.media.
AudioRecorder
本地框架层    AudioSystem    AudioTrack    AudioRecorder
AudioFlinger    IAudioFlinger    IAudioTrack    IAudioRecorder
硬件抽象层    AudioHardwareInterface    AudioStreamOut    AudioStreamIn
AudioTrack.java:SoundPool.java 播放android application的生音资源。
AudioRecord.java: 为android applicatio 提供录音设置(sample、chanel等)的接口;
AudioManager.java: 提供了音频音量,以及播放模式(静音、震动等)的控制。
说明:
1) Audio驱动程序(Linux系统,因不同平台而已)
2) Audio硬件抽象层:hardware/libhardware_legacy/include/hardware/
    AudioHardwareInterface.h(定义Audio硬件抽象层的接口),其中三个主要类AuidoStreamOut/AudioStreamIn/AuidoHardwareInterface实现Audio的输出/输入/管理。
    2.1) AudioStreamOut关键接口write(const void* buffer, size_t bytes)/AudioStreamIn关键接口read(void* buffer, size_t bytes),通过定义内存的指针和长度音频数据的输出和输入。
    2.2) AudioHardwareInterface使用openOutputStream()和openInputStream()函数来获取AudioStreamOut和AudioStreamIn。
    2.3) AudioHardwareInterface中所涉及的参数是在AudioSystem.h中定义,通过setParameters和getParameters接口设置和获取参数,通过setMode()设置系统模式。
    2.4) Audio中引进了策略管理AudioPolicyInterface,目的是将Audio核心部分和辅助性功能分离。
3) AudioFlinger的实现方式
    3.1) 通用方式AndroidHardwareGeneric实现基于特定驱动的通用Audio硬件抽象层。
    3.2) 桩实现方式AndroidHardwareStub,实现Audio硬件抽象层的一个桩,是个空操作,保证没有Audio设备时系统正常工作。
    3.3) AudioDumpInterface实现以文件为输入输出的Audio硬件抽象层,以文件模拟Audio硬件流的输入输出环节。

Audio代码分布:
(1) Java部分:frameworks/base/media/java/android/media
与audio相关的java package是android.media,主要包含audio manager和audio系统的几个类,这部分主要给上层的AP部分提供audio相关的接口。
(2) JNI部分: frameworks/base/core/jni
Android系统会生成一个libandroid_runtime.so,audio的JNI是其中的一个部分。
(3) audio frameworks
头文件路径:frameworks/base/include/media/
代码路径:frameworks/base/media/libmedia/
Audio本地框架是media库的一部分,本部分的内容被编译成库libmedia.so,提供audio部分的接口(其中包括基于binder的IPC机制)。
(4) Audio Flinger:frameworks/base/libs/audioflinger
这部分内容被编译成库libaudioflinger.so,它是audio系统的本地服务部分。
posted @ 2012-06-13 11:08 wanqi Views(163) Comments(0) Edit
Android通过MediaRecorder API手机音频录制例子
import java.io.File;
import java.io.IOException;
import android.media.MediaRecorder;
import android.os.Environment;
public class AudioRecorder
{

final MediaRecorder recorder = new MediaRecorder();
final String path;

public AudioRecorder(String path)
{
this.path = sanitizePath(path);
}

private String sanitizePath(String path)
{
if (!path.startsWith(“/”)) {
path = “/” + path;
}
if (!path.contains(“.”)) {
path += “.3gp”;
}
return Environment.getExternalStorageDirectory().getAbsolutePath() + path;
}

/**
* 开始记录
*/
public void start() throws IOException
{
String state = android.os.Environment.getExternalStorageState();
if(!state.equals(android.os.Environment.MEDIA_MOUNTED)) {
throw new IOException(“SD Card is not mounted. It is ” + state + “.”);
}

File directory = new File(path).getParentFile();
if (!directory.exists() && !directory.mkdirs()) {
throw new IOException(“Path to file could not be created.”);
}

recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile(path);
recorder.prepare();
recorder.start();
}

public void stop() throws IOException {
recorder.stop();
recorder.release();
}
}

抱歉!评论已关闭.