首先看一下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();
}
}