首先看一下android系統的啓動流程:
bootloader
引導程序
kernel
內核
init
init初始化(這個大家都比較熟悉了,不要多說)
loads several daemons and services, including zygotesee /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秒,所以我們重點能夠修理的就是這兩個老大的。
一、首先是調試工具的使用,可以測試哪些類和那些過程佔用了多少時間,
主要工具爲
stopwatchMessage loggers
grabserial
參考http://elinux.org/Grabserial
printk times
參考http://elinux.org/Printk_Times
logcatAndroid自帶
bootchart
參考http://elinux.org/Bootchart 和 http://elinux.org/Bootchart
straceAOSP的一部分(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();
}
}