Android重量級開發之--提高android啓動速度研究

大家都知道啓動速度慢是智能操作系統的一個通病,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();
}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章