第一部分 MediaPlayer概述
Android的MediaPlayer包含了Audio和video的播放功能,在Android的界面上,Music和Video两个应用程序都是调用MediaPlayer实现的。
MediaPlayer在底层是基于OpenCore(PacketVideo)的库实现的,为了构建一个MediaPlayer程序,上层还包含了进程间通讯等内容,这种进程间通讯的基础是Android基本库中的Binder机制。
以开源的Android为例MediaPlayer的代码主要在以下的目录中:
JAVA程序的路径:
packages/apps/Music/src/com/android/music/

JAVA Framework的路径:
frameworks/base/media/java /android/media/MediaPlayer.java

JAVA本地调用部分(JNI):
frameworks/base/media/jni/android_media_MediaPlayer.cpp
这部分内容编译成为目标是libmedia_jni.so

主要的头文件在以下的目录中:
frameworks/base/include/media/

多媒体底层库在以下的目录中:
frameworks/base/media/libmedia/
这部分的内容被编译成库libmedia.so

多媒体服务部分:
frameworks/base/media/libmediaplayerservice/
文件为mediaplayerservice.h和mediaplayerservice.cpp
这部分内容被编译成库libmediaplayerservice.so

基于OpenCore的多媒体播放器部分
external/opencore/
这部分内容被编译成库libopencoreplayer.so

从程序规模上来看,libopencoreplayer.so 是主要的实现部分,而其他的库基本上都是在其上建立的封装和为建立进程间通讯的机制。

第二部分 MediaPlayer的接口与架构

2.1 整体框架图
MediaPlayer的各个库之间的结构比较复杂,可以用下图的表示:
下载 (73 KB)
2009-5-13 15:09
在各个库中,libmedia.so位于核心的位置,它对上层的提供的接口主要是MediaPlayer类,类libmedia_jni.so通过调用 MediaPlayer类提供对JAVA的接口,并且实现了android.media.MediaPlayer类。
libmediaplayerservice.so是Media的服务器,它通过继承libmedia.so的类实现服务器的功能,而libmedia.so中的另外一部分内容则通过进程间通讯和libmediaplayerservice.so进行通讯。
libmediaplayerservice.so的真正功能通过调用OpenCore Player来完成。
MediaPlayer部分的头文件在frameworks/base/include/media/ 目录中,这个目录是和libmedia.so库源文件的目录frameworks/base/media/libmedia/ 相对应的。主要的头文件有以下几个:
■IMediaPlayerClient.h
■mediaplayer.h
■IMediaPlayer.h
■IMediaPlayerService.h
■MediaPlayerInterface.h
在这些头文件mediaplayer.h提供了对上层的接口,而其他的几个头文件都是提供一些接口类(即包含了纯虚函数的类),这些接口类必须被实现类继承才能够使用。
整个MediaPlayer库和调用的关系如下图所示:

下载 (158 KB)
2009-5-13 15:09
整个MediaPlayer在运行的时候,可以大致上分成Client和Server两个部分,它们分别在两个进程中运行,它们之间使用Binder机制 实现IPC通讯。从框架结构上来看,IMediaPlayerService.h、IMediaPlayerClient.h和 MediaPlayer.h三个类定义了MeidaPlayer的接口和架构,MediaPlayerService.cpp和 mediaplayer.cpp两个文件用于MeidaPlayer架构的实现,MeidaPlayer的具体功能在PVPlayer(库 libopencoreplayer.so)中的实现。

2.2 头文件IMediaPlayerClient.h
IMediaPlayerClient.h用于描述一个MediaPlayer客户端的接口,描述如下所示:
  1. class IMediaPlayerClient: public IInterface
  2. {
  3. public:
  4. DECLARE_META_INTERFACE(MediaPlayerClient);
  5. virtual void notify(int msg, int ext1, int ext2) = 0;
  6. };
  7. class BnMediaPlayerClient: public BnInterface<IMediaPlayerClient>
  8. {
  9. public:
  10. virtual status_t onTransact( uint32_t code,
  11. const Parcel& data,
  12. Parcel* reply,
  13. uint32_t flags = 0);
  14. };

在定义中,IMediaPlayerClient类继承IInterface,并定义了一个MediaPlayer客户端的接 口,BnMediaPlayerClient继承了BnInterface<IMediaPlayerClient>,这是为基于 Android的基础类Binder机制实现在进程通讯而构建的。事实上,根据BnInterface类模版的定义 BnInterface<IMediaPlayerClient>类相当于双继承了BnInterface和 ImediaPlayerClient。这是Android一种常用的定义方式。


2.3 头文件mediaplayer.h
mediaplayer.h是对外的接口类,它最主要是定义了一个MediaPlayer类:
  1. class MediaPlayer : public BnMediaPlayerClient
  2. {
  3. public:
  4. MediaPlayer();
  5. ~MediaPlayer();
  6. void onFirstRef();
  7. void disconnect();
  8. status_t setDataSource(const char *url);
  9. status_t setDataSource(int fd, int64_t offset, int64_t length);
  10. status_t setVideoSurface(const sp<Surface>& surface);
  11. status_t setListener(const sp<MediaPlayerListener>&listener);
  12. status_t prepare();
  13. status_t prepareAsync();
  14. status_t start();
  15. status_t stop();
  16. status_t pause();
  17. bool isPlaying();
  18. status_t getVideoWidth(int *w);
  19. status_t getVideoHeight(int *h);
  20. status_t seekTo(int msec);
  21. status_t getCurrentPosition(int *msec);
  22. status_t getDuration(int *msec);
  23. status_t reset();
  24. status_t setAudioStreamType(int type);
  25. status_t setLooping(int loop);
  26. status_t setVolume(float leftVolume, float rightVolume);
  27. void notify(int msg, int ext1, int ext2);
  28. static sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels);
  29. static sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels);
  30. //……
  31. }
从接口中可以看出MediaPlayer类刚好实现了一个MediaPlayer的基本操作,例如播放(start)、停止(stop)、暂停(pause)等。
另外的一个类DeathNotifier在MediaPlayer类中定义,它继承了IBinder类中的DeathRecipient类:
  1. class DeathNotifier: public IBinder::DeathRecipient
  2. {
  3. public:
  4. DeathNotifier() {}
  5. virtual ~DeathNotifier();
  6. virtual void binderDied(const wp<IBinder>& who);
  7. };
事实上,MediaPlayer类正是间接地继承了IBinder,而MediaPlayer::DeathNotifier类继承了IBinder::DeathRecipient,这都是为了实现进程间通讯而构建的。
这个库和主要类之间的关系如下所示:


2.4 头文件IMediaPlayer.h
IMediaPlayer.h主要的的内容是一个实现MediaPlayer功能的接口,它的主要定义如下所示:
  1. class IMediaPlayer: public IInterface
  2. {
  3. public:
  4. DECLARE_META_INTERFACE(MediaPlayer);
  5. virtual void disconnect() = 0;
  6. virtual status_t setVideoSurface(const sp<ISurface>& surface) = 0;
  7. virtual status_t prepareAsync() = 0;
  8. virtual status_t start() = 0;
  9. virtual status_t stop() = 0;
  10. virtual status_t pause() = 0;
  11. virtual status_t isPlaying(bool* state) = 0;
  12. virtual status_t getVideoSize(int* w, int* h) = 0;
  13. virtual status_t seekTo(int msec) = 0;
  14. virtual status_t getCurrentPosition(int* msec) = 0;
  15. virtual status_t getDuration(int* msec) = 0;
  16. virtual status_t reset() = 0;
  17. virtual status_t setAudioStreamType(int type) = 0;
  18. virtual status_t setLooping(int loop) = 0;
  19. virtual status_t setVolume(float leftVolume, float rightVolume) = 0;
  20. };
  21. class BnMediaPlayer: public BnInterface<IMediaPlayer>
  22. {
  23. public:
  24. virtual status_t onTransact( uint32_t code,
  25. const Parcel& data,
  26. Parcel* reply,
  27. uint32_t flags = 0);
  28. };

在 IMediaPlayer类中,主要定义MediaPlayer的功能接口,这个类必须被继承才能够使用。值得注意的是,这些接口和 MediaPlayer类的接口有些类似,但是它们并没有直接的关系。事实上,在MediaPlayer类的各种实现中,一般都会通过调用 IMediaPlayer类的实现类来完成。

2.5 头文件IMediaPlayerService.h

2.5 头文件IMediaPlayerService.h
IMediaPlayerService.h用于描述一个MediaPlayer的服务,定义方式如下所示:
  1. class IMediaPlayerService: public IInterface
  2. {
  3. public:
  4. DECLARE_META_INTERFACE(MediaPlayerService);
  5. virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, const char* url) = 0;
  6. virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length) = 0;
  7. virtual sp<IMemory>decode(const char* url, uint32_t *pSampleRate, int* pNumChannels) = 0;
  8. virtual sp<IMemory>decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels) = 0;
  9. };
  10. class BnMediaPlayerService: public BnInterface<IMediaPlayerService>
  11. {
  12. public:
  13. virtual status_tonTransact( uint32_t code,
  14. const Parcel& data,
  15. Parcel* reply,
  16. uint32_t flags = 0);
  17. };
由 于具有纯虚函数,IMediaPlayerService 以及BnMediaPlayerService必须被继承实现才能够使用,在IMediaPlayerService定义的create和decode等 接口,事实上是必须被继承者实现的内容。注意,create的返回值的类型是sp<IMediaPlayer>,这个 IMediaPlayer正是提供实现功能的接口。

2.6 头文件MediaPlayerInterface.h
MediaPlayerInterface.h定义的是一个MeidaPlayer底层的接口,这个类的实现者是最终实现媒体播放功能的。
MediaPlayerBase是一个基础的接口类,其主要的接口如下所示:
  1. class MediaPlayerBase : public RefBase
  2. {
  3. public:
  4. // ……
  5. MediaPlayerBase() : mCookie(0), mNotify(0) {}
  6. virtual ~MediaPlayerBase() {}
  7. virtual status_t initCheck() = 0;
  8. virtual bool hardwareOutput() = 0;
  9. virtual status_t setSigBusHandlerStructTLSKey(pthread_key_t key) { return 0; }
  10. virtual status_t setDataSource(const char *url) = 0;
  11. virtual status_t setDataSource(int fd, int64_t offset, int64_t length) = 0;
  12. virtual status_t setVideoSurface(const sp<ISurface>& surface) = 0;
  13. virtual status_t prepare() = 0;
  14. virtual status_t prepareAsync() = 0;
  15. virtual status_t start() = 0;
  16. virtual status_t stop() = 0;
  17. virtual status_t pause() = 0;
  18. virtual bool isPlaying() = 0;
  19. virtual status_t getVideoWidth(int *w) {return 0;}
  20. virtual status_t getVideoHeight(int *h) {return 0;}
  21. virtual status_t seekTo(int msec) = 0;
  22. virtual status_t getCurrentPosition(int *msec) = 0;
  23. virtual status_t getDuration(int *msec) = 0;
  24. virtual status_t reset() = 0;
  25. virtual status_t setLooping(int loop) = 0;
  26. virtual player_type playerType() = 0;
  27. virtual void setNotifyCallback(void* cookie, notify_callback_f notifyFunc) {
  28. mCookie = cookie; mNotify = notifyFunc; }
  29. // ……
  30. };
MediaPlayerInterface继承了类MediaPlayerBase:
  1. class MediaPlayerInterface : public MediaPlayerBase
  2. {
  3. public:
  4. virtual ~MediaPlayerInterface() { }
  5. virtual bool hardwareOutput() { return false; }
  6. virtual void setAudioSink(const sp<AudioSink>& audioSink) { mAudioSink = audioSink; }
  7. protected:
  8. sp<AudioSink> mAudioSink;
  9. };
MediaPlayerInterface本身还是一个纯虚类,只作为接口使用。MediaPlayerBase和MediaPlayerInterface是底层的接口,这些接口被MediaPlayer的服务所调用,用于实现MediaPlayer的具体功能。

第三部分 MediaPlayer的主要实现分析

第三部分 MediaPlayer的主要实现分析

3.1 JAVA程序部分
packages/apps/Music/src/com/android/music/ 目录的MediaPlaybackService.java文件中,包含了对MediaPlayer的调用。
在MediaPlaybackService.java中包含对包的引用:
  1. import android.media.MediaPlayer;
在MediaPlaybackService类的内部,定义了MultiPlayer类:
  1. private class MultiPlayer {
  2. private MediaPlayer mMediaPlayer = new MediaPlayer();
  3. }
MultiPlayer类中使用了MediaPlayer类,其中有一些对这个MediaPlayer的调用,调用的过程如下所示:
  1. mMediaPlayer.reset();
  2. mMediaPlayer.setDataSource(path);
  3. mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
reset、setDataSource和setAudioStreamType等接口就是通过JAVA本地调用(JNI)来实现的。

3.2 MediaPlayer的JAVA本地调用部分
MediaPlayer的JAVA本地调用部分在目录frameworks/base/media/jni/的android_media_MediaPlayer.cpp中的文件中实现。
android_media_MediaPlayer.cpp之中定义了一个JNINativeMethod(JAVA本地调用方法)类型的数组gMethods,如下所示:
  1. static JNINativeMethod gMethods[] = {
  2. {"setDataSource", "(Ljava/lang/String;)V",(void *)android_media_MediaPlayer_setDataSource},
  3. {"setDataSource", "(Ljava/io/FileDescriptor;JJ)V",(void *)android_media_MediaPlayer_setDataSourceFD},
  4. {"prepare", "()V",(void *)android_media_MediaPlayer_prepare},
  5. {"prepareAsync","()V",(void *)android_media_MediaPlayer_prepareAsync},
  6. {"_start","()V",(void *)android_media_MediaPlayer_start},
  7. {"_stop", "()V",(void *)android_media_MediaPlayer_stop},
  8. {"getVideoWidth", "()I",(void *)android_media_MediaPlayer_getVideoWidth},
  9. {"getVideoHeight","()I",(void *)android_media_MediaPlayer_getVideoHeight},
  10. {"seekTo","(I)V", (void *)android_media_MediaPlayer_seekTo},
  11. {"_pause","()V",(void *)android_media_MediaPlayer_pause},
  12. {"isPlaying", "()Z",(void *)android_media_MediaPlayer_isPlaying},
  13. {"getCurrentPosition","()I",(void *)android_media_MediaPlayer_getCurrentPosition},
  14. {"getDuration", "()I",(void *)android_media_MediaPlayer_getDuration},
  15. {"_release","()V",(void *)android_media_MediaPlayer_release},
  16. {"_reset","()V",(void *)android_media_MediaPlayer_reset},
  17. {"setAudioStreamType","(I)V", (void *)android_media_MediaPlayer_setAudioStreamType},
  18. {"setLooping","(Z)V", (void *)android_media_MediaPlayer_setLooping},
  19. {"setVolume", "(FF)V",(void *)android_media_MediaPlayer_setVolume},
  20. {"getFrameAt","(I)Landroid/graphics/Bitmap;", (void *)android_media_MediaPlayer_getFrameAt},
  21. {"native_setup","(Ljava/lang/Object;)V",(void *)android_media_MediaPlayer_native_setup},
  22. {"native_finalize", "()V",(void *)android_media_MediaPlayer_native_finalize},
JNINativeMethod的第一个成员是一个字符串,表示了JAVA本地调用方法的名称,这个名称是在JAVA程序中调用的名称;第二个成员也是一个字符串,表示JAVA本地调用方法的参数和返回值;第三个成员是JAVA本地调用方法对应的C语言函数。
其中android_media_MediaPlayer_reset函数的实现如下所示:
  1. static void
  2. android_media_MediaPlayer_reset(JNIEnv *env, jobject thiz)
  3. {
  4. sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
  5. if (mp == NULL ) {
  6. jniThrowException(env, "java/lang/IllegalStateException", NULL);
  7. return;
  8. }
  9. process_media_player_call( env, thiz, mp->reset(), NULL, NULL );
  10. }
在android_media_MediaPlayer_reset的调用中,得到一个MediaPlayer指针,通过对它的调用实现实际的功能。
register_android_media_MediaPlayer用于将gMethods注册为的类"android/media/MediaPlayer",其实现如下所示。
  1. static int register_android_media_MediaPlayer(JNIEnv *env)
  2. {
  3. jclass clazz;
  4. clazz = env->FindClass("android/media/MediaPlayer");
  5. // ......
  6. return AndroidRuntime::registerNativeMethods(env, "android/media/MediaPlayer", gMethods, NELEM(gMethods));
  7. }
"android/media/MediaPlayer"对应JAVA的类android.media.MediaPlayer。



3.3 mediaplayer本地库libmedia.so
libs/media/mediaplayer.cpp文件用于实现mediaplayer.h提供的接口,其中一个重要的片段如下所示:
  1. const sp<IMediaPlayerService>& MediaPlayer::getMediaPlayerService()
  2. {
  3. Mutex::Autolock _l(mServiceLock);
  4. if (mMediaPlayerService.get() == 0) {
  5. sp<IServiceManager> sm = defaultServiceManager();
  6. sp<IBinder> binder;
  7. do {
  8. binder = sm->getService(String16("media.player"));
  9. if (binder != 0)
  10. break;
  11. LOGW("MediaPlayerService not published, waiting...");
  12. usleep(500000); // 0.5 s
  13. } while(true);
  14. if (mDeathNotifier == NULL) {
  15. mDeathNotifier = new DeathNotifier();
  16. }
  17. binder->linkToDeath(mDeathNotifier);
  18. mMediaPlayerService = interface_cast<IMediaPlayerService>(binder);
  19. }
  20. LOGE_IF(mMediaPlayerService==0, "no MediaPlayerService!?");
  21. return mMediaPlayerService;
  22. }
其 中最重要的一点是binder = sm->getService(String16("media.player"));这个调用用来得到一个名称为"media.player"的 服务,这个调用返回值的类型为IBinder,根据实现将其转换成类型IMediaPlayerService使用。
一个具体的函数setDataSource如下所示:
  1. status_t MediaPlayer::setDataSource(const char *url)
  2. {
  3. LOGV("setDataSource(%s)", url);
  4. status_t err = UNKNOWN_ERROR;
  5. if (url != NULL) {
  6. const sp<IMediaPlayerService>& service(getMediaPlayerService());
  7. if (service != 0) {
  8. sp<IMediaPlayer> player(service->create(getpid(), this, url));
  9. err = setDataSource(player);
  10. }
  11. }
  12. return err;
  13. }
在函数setDataSource函数中,调用getMediaPlayerService得到了一个IMediaPlayerService,又从IMediaPlayerService中得到了IMediaPlayer类型的指针,通过这个指针进行着具体的操作。
其他一些函数的实现也与setDataSource类似。
libmedia.so中的其他一些文件与头文件的名称相同,它们是:
libs/media/IMediaPlayerClient.cpp
libs/media/IMediaPlayer.cpp
libs/media/IMediaPlayerService.cpp
为了实现Binder的具体功能,在这些类中还需要实现一个BpXXX的类,例如IMediaPlayerClient.cpp的实现如下所示:
  1. class BpMediaPlayerClient: public BpInterface<IMediaPlayerClient>
  2. {
  3. public:
  4. BpMediaPlayerClient(const sp<IBinder>& impl)
  5. : BpInterface<IMediaPlayerClient>(impl)
  6. {
  7. }
  8. virtual void notify(int msg, int ext1, int ext2)
  9. {
  10. Parcel data, reply;
  11. data.writeInterfaceToken(IMediaPlayerClient::getInterfaceDescriptor());
  12. data.writeInt32(msg);
  13. data.writeInt32(ext1);
  14. data.writeInt32(ext2);
  15. remote()->transact(NOTIFY, data, &reply, IBinder::FLAG_ONEWAY);
  16. }
  17. };
还需要实现定义宏IMPLEMENT_META_INTERFACE,这个宏将被展开,生成几个函数:
  1. IMPLEMENT_META_INTERFACE(MediaPlayerClient, "android.hardware.IMediaPlayerClient");

以上的实现都是基于Binder框架的实现方式,只需要按照模版实现即可。其中BpXXX的类为代理类(proxy),BnXXX的类为本地类(native)。代理类的transact函数和本地类的onTransact函数实现对应的通讯。

3.4 media服务libmediaservice.so

3.4 media服务libmediaservice.so
frameworks/base/media/libmediaplayerservice目录中的MediaPlayerService.h和MediaPlayerService.cpp用于实现一个
servers/media/的服务,MediaPlayerService是继承BnMediaPlayerService的实现,在这个类的内部又定义了类Client,MediaPlayerService::Client继承了BnMediaPlayer。
  1. class MediaPlayerService : public BnMediaPlayerService
  2. {
  3. class Client : public BnMediaPlayer
  4. }
在MediaPlayerService中具有如下一个静态函数instantiate:
  1. void MediaPlayerService::instantiate() {
  2. defaultServiceManager()->addService(
  3. String16("media.player"), new MediaPlayerService());
  4. }
在instantiate函数中,调用IServiceManager的一个函数addService,向其中增加了一个名为"media.player"的服务。
这个名为"media.player"的服务和mediaplayer.cpp中调用getService中得到的使用一样名称。因此,在这里调用 addService增加服务在mediaplayer.cpp中可以按照名称"media.player"来使用。这就是使用Binder实现进程间通 讯的(IPC)的作用,事实上这个MediaPlayerService类是在服务中运行的,而mediaplayer.cpp调用的功能在应用中运行, 二者并不是一个进程。但是在mediaplayer.cpp却像一个进程的调用一样调用MediaPlayerService的功能。
在MediaPlayerService.cpp中的createPlayer函数如下所示:
  1. static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie,
  2. notify_callback_f notifyFunc)
  3. {
  4. sp<MediaPlayerBase> p;
  5. switch (playerType) {
  6. case PV_PLAYER:
  7. LOGV(" create PVPlayer");
  8. p = new PVPlayer();
  9. break;
  10. case SONIVOX_PLAYER:
  11. LOGV(" create MidiFile");
  12. p = new MidiFile();
  13. break;
  14. case VORBIS_PLAYER:
  15. LOGV(" create VorbisPlayer");
  16. p = new VorbisPlayer();
  17. break;
  18. }
  19. //……
  20. return p;
  21. }

在 这里根据playerType的类型建立不同的播放器:对于大多数情况,类型将是PV_PLAYER,这时会调用了new PVPlayer()建立一个PVPlayer,然后将其指针转换成MediaPlayerBase来使用;对于Mini文件的情况,类型为 SONIVOX_PLAYER,将会建立一个MidiFile;对于Ogg Vorbis格式的情况,将会建立一个VorbisPlayer。
值得注意的是PVPlayer、MidiFile和VorbisPlayer三个类都是继承MediaPlayerInterface得到的,而 MediaPlayerInterface又是继承MediaPlayerBase得到的,因此三者具有相同接口类型。只有建立的时候会调用各自的构造函 数,在建立之后,将只通过MediaPlayerBase接口来MediaPlayerBase控制它们。
在frameworks/base/media/libmediaplayerservice目录中,MidiFile.h和MidiFile.cpp的实现
MidiFile,VorbisPlayer.h和VorbisPlayer.cpp实现一个VorbisPlayer。




3.5 OpenCore Player的实现libopencoreplayer.so
OpenCore Player在external/opencore/中实现,这个实现是一个基于OpenCore的Player的实现。具体实现的文件为 playerdriver.cpp。其中实现了两个类:PlayerDriver和PVPlayer。PVPlayer通过调用PlayerDriver 的函数实现具体的功能。

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. python list.sort()根据多个关键字排序的方法实现
  3. Android(安卓)RIL结构分析与移植
  4. android使用HttpClient和URLConnection获取网页内容
  5. Android实现数据存储技术
  6. 【Android】实现动态显示隐藏密码输入框的内容
  7. Android通过OpenSL ES播放音频套路详解
  8. android实现自定义相机以及图片的水印
  9. Android帧缓冲区(Frame Buffer)硬件抽象层(HAL) 模块Gralloc的实现原

随机推荐

  1. Android杀死正在运行的进程
  2. Http的请求方式
  3. Android中资源限定符hdpi large等的优先
  4. [Android环境搭建] 申请Android(安卓)Map
  5. Android端通过httpCilent访问Tomcat服务
  6. Android初级教程三个Dialog对话框小案例
  7. adb命令的学习
  8. 【Android】之【对话框(Dialog)大全】
  9. 【Android学习笔记】Spinner
  10. 使用Menu制作弹出菜单