原文出处——>[Android应用程序框架层和系统运行库层日志系统源代码分析](http://blog.csdn.net/luoshengyang/article/details/6598703)
在开发Android应用程序时,少不了使用Log来监控和调试程序的执行。在上一篇文章Android日志系统驱动程序Logger源代码分析中,我们分析了驱动程序Logger的源代码,在前面的文章浅谈Android系统开发中Log的使用一文,我们也简单介绍在应用程序中使Log的方法,在这篇文章中,我们将详细介绍Android应用程序框架层和系统运行库存层日志系统的源代码,使得我们可以更好地理解Android的日志系统的实现。
我们在Android应用程序,一般是调用应用程序框架层的Java接口(android.util.Log)来使用日志系统,这个Java接口通过JNI方法和系统运行库最终调用内核驱动程序Logger把Log写到内核空间中。按照这个调用过程,我们一步步介绍Android应用程序框架层日志系统的源代码。学习完这个过程之后,我们可以很好地理解Android系统的架构,即应用程序层(Application)的接口是如何一步一步地调用到内核空间的。
**一. 应用程序框架层日志系统Java接口的实现。**
在浅谈Android系统开发中Log的使用一文中,我们曾经介绍过Android应用程序框架层日志系统的源代码接口。这里,为了描述方便和文章的完整性,我们重新贴一下这部份的代码,在**frameworks/base/core/java/android/util/Log.java**文件中,实现日志系统的Java接口:
~~~
................................................
public final class Log {
................................................
/**
* Priority constant for the println method; use Log.v.
*/
public static final int VERBOSE = 2;
/**
* Priority constant for the println method; use Log.d.
*/
public static final int DEBUG = 3;
/**
* Priority constant for the println method; use Log.i.
*/
public static final int INFO = 4;
/**
* Priority constant for the println method; use Log.w.
*/
public static final int WARN = 5;
/**
* Priority constant for the println method; use Log.e.
*/
public static final int ERROR = 6;
/**
* Priority constant for the println method.
*/
public static final int ASSERT = 7;
.....................................................
public static int v(String tag, String msg) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}
public static int v(String tag, String msg, Throwable tr) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg + '\n' + getStackTraceString(tr));
}
public static int d(String tag, String msg) {
return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
public static int d(String tag, String msg, Throwable tr) {
return println_native(LOG_ID_MAIN, DEBUG, tag, msg + '\n' + getStackTraceString(tr));
}
public static int i(String tag, String msg) {
return println_native(LOG_ID_MAIN, INFO, tag, msg);
}
public static int i(String tag, String msg, Throwable tr) {
return println_native(LOG_ID_MAIN, INFO, tag, msg + '\n' + getStackTraceString(tr));
}
public static int w(String tag, String msg) {
return println_native(LOG_ID_MAIN, WARN, tag, msg);
}
public static int w(String tag, String msg, Throwable tr) {
return println_native(LOG_ID_MAIN, WARN, tag, msg + '\n' + getStackTraceString(tr));
}
public static int w(String tag, Throwable tr) {
return println_native(LOG_ID_MAIN, WARN, tag, getStackTraceString(tr));
}
public static int e(String tag, String msg) {
return println_native(LOG_ID_MAIN, ERROR, tag, msg);
}
public static int e(String tag, String msg, Throwable tr) {
return println_native(LOG_ID_MAIN, ERROR, tag, msg + '\n' + getStackTraceString(tr));
}
..................................................................
/** @hide */ public static native int LOG_ID_MAIN = 0;
/** @hide */ public static native int LOG_ID_RADIO = 1;
/** @hide */ public static native int LOG_ID_EVENTS = 2;
/** @hide */ public static native int LOG_ID_SYSTEM = 3;
/** @hide */ public static native int println_native(int bufID,
int priority, String tag, String msg);
}
~~~
定义了2~7一共6个日志优先级别ID和4个日志缓冲区ID。回忆一下Android日志系统驱动程序Logger源代码分析一文,在Logger驱动程序模块中,定义了log_main、log_events和log_radio三个日志缓冲区,分别对应三个设备文件/dev/log/main、/dev/log/events和/dev/log/radio。这里的4个日志缓冲区的前面3个ID就是对应这三个设备文件的文件描述符了,在下面的章节中,我们将看到这三个文件描述符是如何创建的。在下载下来的Android内核源代码中,第4个日志缓冲区LOG_ID_SYSTEM并没有对应的设备文件,在这种情况下,它和LOG_ID_MAIN对应同一个缓冲区ID,在下面的章节中,我们同样可以看到这两个ID是如何对应到同一个设备文件的。
在整个Log接口中,最关键的地方声明了println_native本地方法,所有的Log接口都是通过调用这个本地方法来实现Log的定入。下面我们就继续分析这个本地方法println_native。
**二. 应用程序框架层日志系统JNI方法的实现。**
在**frameworks/base/core/jni/android_util_Log.cpp**文件中,实现JNI方法println_native:
~~~
/* //device/libs/android_runtime/android_util_Log.cpp
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
#define LOG_NAMESPACE "log.tag."
#define LOG_TAG "Log_println"
#include <assert.h>
#include <cutils/properties.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include "jni.h"
#include "utils/misc.h"
#include "android_runtime/AndroidRuntime.h"
#define MIN(a,b) ((a<b)?a:b)
namespace android {
struct levels_t {
jint verbose;
jint debug;
jint info;
jint warn;
jint error;
jint assert;
};
static levels_t levels;
static int toLevel(const char* value)
{
switch (value[0]) {
case 'V': return levels.verbose;
case 'D': return levels.debug;
case 'I': return levels.info;
case 'W': return levels.warn;
case 'E': return levels.error;
case 'A': return levels.assert;
case 'S': return -1; // SUPPRESS
}
return levels.info;
}
static jboolean android_util_Log_isLoggable(JNIEnv* env, jobject clazz, jstring tag, jint level)
{
#ifndef HAVE_ANDROID_OS
return false;
#else /* HAVE_ANDROID_OS */
int len;
char key[PROPERTY_KEY_MAX];
char buf[PROPERTY_VALUE_MAX];
if (tag == NULL) {
return false;
}
jboolean result = false;
const char* chars = env->GetStringUTFChars(tag, NULL);
if ((strlen(chars)+sizeof(LOG_NAMESPACE)) > PROPERTY_KEY_MAX) {
jclass clazz = env->FindClass("java/lang/IllegalArgumentException");
char buf2[200];
snprintf(buf2, sizeof(buf2), "Log tag \"%s\" exceeds limit of %d characters\n",
chars, PROPERTY_KEY_MAX - sizeof(LOG_NAMESPACE));
// release the chars!
env->ReleaseStringUTFChars(tag, chars);
env->ThrowNew(clazz, buf2);
return false;
} else {
strncpy(key, LOG_NAMESPACE, sizeof(LOG_NAMESPACE)-1);
strcpy(key + sizeof(LOG_NAMESPACE) - 1, chars);
}
env->ReleaseStringUTFChars(tag, chars);
len = property_get(key, buf, "");
int logLevel = toLevel(buf);
return (logLevel >= 0 && level >= logLevel) ? true : false;
#endif /* HAVE_ANDROID_OS */
}
/*
* In class android.util.Log:
* public static native int println_native(int buffer, int priority, String tag, String msg)
*/
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
const char* tag = NULL;
const char* msg = NULL;
if (msgObj == NULL) {
jclass npeClazz;
npeClazz = env->FindClass("java/lang/NullPointerException");
assert(npeClazz != NULL);
env->ThrowNew(npeClazz, "println needs a message");
return -1;
}
if (bufID < 0 || bufID >= LOG_ID_MAX) {
jclass npeClazz;
npeClazz = env->FindClass("java/lang/NullPointerException");
assert(npeClazz != NULL);
env->ThrowNew(npeClazz, "bad bufID");
return -1;
}
if (tagObj != NULL)
tag = env->GetStringUTFChars(tagObj, NULL);
msg = env->GetStringUTFChars(msgObj, NULL);
int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
if (tag != NULL)
env->ReleaseStringUTFChars(tagObj, tag);
env->ReleaseStringUTFChars(msgObj, msg);
return res;
}
/*
* JNI registration.
*/
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
};
int register_android_util_Log(JNIEnv* env)
{
jclass clazz = env->FindClass("android/util/Log");
if (clazz == NULL) {
LOGE("Can't find android/util/Log");
return -1;
}
levels.verbose = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "VERBOSE", "I"));
levels.debug = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "DEBUG", "I"));
levels.info = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "INFO", "I"));
levels.warn = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "WARN", "I"));
levels.error = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ERROR", "I"));
levels.assert = env->GetStaticIntField(clazz, env->GetStaticFieldID(clazz, "ASSERT", "I"));
return AndroidRuntime::registerNativeMethods(env, "android/util/Log", gMethods, NELEM(gMethods));
}
}; // namespace android
~~~
在gMethods变量中,定义了println_native本地方法对应的函数调用是android_util_Log_println_native。在android_util_Log_println_native函数中,通过了各项参数验证正确后,就调用运行时库函数__android_log_buf_write来实现Log的写入操作。__android_log_buf_write函实实现在liblog库中,它有4个参数,分别缓冲区ID、优先级别ID、Tag字符串和Msg字符串。下面运行时库liblog中的__android_log_buf_write的实现。
**三. 系统运行库层日志系统的实现。**
在系统运行库层liblog库的实现中,内容比较多,这里,我们只关注日志写入操作__android_log_buf_write的相关实现:
~~~
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
{
struct iovec vec[3];
if (!tag)
tag = "";
/* XXX: This needs to go! */
if (!strcmp(tag, "HTC_RIL") ||
!strncmp(tag, "RIL", 3) || /* Any log tag with "RIL" as the prefix */
!strcmp(tag, "AT") ||
!strcmp(tag, "GSM") ||
!strcmp(tag, "STK") ||
!strcmp(tag, "CDMA") ||
!strcmp(tag, "PHONE") ||
!strcmp(tag, "SMS"))
bufID = LOG_ID_RADIO;
vec[0].iov_base = (unsigned char *) &prio;
vec[0].iov_len = 1;
vec[1].iov_base = (void *) tag;
vec[1].iov_len = strlen(tag) + 1;
vec[2].iov_base = (void *) msg;
vec[2].iov_len = strlen(msg) + 1;
return write_to_log(bufID, vec, 3);
}
~~~
函数首先是检查传进来的tag参数是否是为HTC_RIL、RIL、AT、GSM、STK、CDMA、PHONE和SMS中的一个,如果是,就无条件地使用ID为LOG_ID_RADIO的日志缓冲区作为写入缓冲区,接着,把传进来的参数prio、tag和msg分别存放在一个向量数组中,调用write_to_log函数来进入下一步操作。write_to_log是一个函数指针,定义在文件开始的位置上:
~~~
static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;
~~~
并且初始化为__write_to_log_init函数:
~~~
static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
{
#ifdef HAVE_PTHREADS
pthread_mutex_lock(&log_init_lock);
#endif
if (write_to_log == __write_to_log_init) {
log_fds[LOG_ID_MAIN] = log_open("/dev/"LOGGER_LOG_MAIN, O_WRONLY);
log_fds[LOG_ID_RADIO] = log_open("/dev/"LOGGER_LOG_RADIO, O_WRONLY);
log_fds[LOG_ID_EVENTS] = log_open("/dev/"LOGGER_LOG_EVENTS, O_WRONLY);
log_fds[LOG_ID_SYSTEM] = log_open("/dev/"LOGGER_LOG_SYSTEM, O_WRONLY);
write_to_log = __write_to_log_kernel;
if (log_fds[LOG_ID_MAIN] < 0 || log_fds[LOG_ID_RADIO] < 0 ||
log_fds[LOG_ID_EVENTS] < 0) {
log_close(log_fds[LOG_ID_MAIN]);
log_close(log_fds[LOG_ID_RADIO]);
log_close(log_fds[LOG_ID_EVENTS]);
log_fds[LOG_ID_MAIN] = -1;
log_fds[LOG_ID_RADIO] = -1;
log_fds[LOG_ID_EVENTS] = -1;
write_to_log = __write_to_log_null;
}
if (log_fds[LOG_ID_SYSTEM] < 0) {
log_fds[LOG_ID_SYSTEM] = log_fds[LOG_ID_MAIN];
}
}
#ifdef HAVE_PTHREADS
pthread_mutex_unlock(&log_init_lock);
#endif
return write_to_log(log_id, vec, nr);
}
~~~
这里我们可以看到,如果是第一次调write_to_log函数,write_to_log == __write_to_log_init判断语句就会true,于是执行log_open函数打开设备文件,并把文件描述符保存在log_fds数组中。如果打开/dev/LOGGER_LOG_SYSTEM文件失败,即log_fds[LOG_ID_SYSTEM] < 0,就把log_fds[LOG_ID_SYSTEM]设置为log_fds[LOG_ID_MAIN],这就是我们上面描述的如果不存在ID为LOG_ID_SYSTEM的日志缓冲区,就把LOG_ID_SYSTEM设置为和LOG_ID_MAIN对应的日志缓冲区了。LOGGER_LOG_MAIN、LOGGER_LOG_RADIO、LOGGER_LOG_EVENTS和LOGGER_LOG_SYSTEM四个宏定义在system/core/include/cutils/logger.h文件中:
~~~
#define LOGGER_LOG_MAIN "log/main"
#define LOGGER_LOG_RADIO "log/radio"
#define LOGGER_LOG_EVENTS "log/events"
#define LOGGER_LOG_SYSTEM "log/system"
~~~
接着,把write_to_log函数指针指向__write_to_log_kernel函数:
~~~
static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
{
ssize_t ret;
int log_fd;
if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) {
log_fd = log_fds[(int)log_id];
} else {
return EBADF;
}
do {
ret = log_writev(log_fd, vec, nr);
} while (ret < 0 && errno == EINTR);
return ret;
}
~~~
函数调用log_writev来实现Log的写入,注意,这里通过一个循环来写入Log,直到写入成功为止。这里log_writev是一个宏,在文件开始的地方定义为:
~~~
#if FAKE_LOG_DEVICE
// This will be defined when building for the host.
#define log_open(pathname, flags) fakeLogOpen(pathname, flags)
#define log_writev(filedes, vector, count) fakeLogWritev(filedes, vector, count)
#define log_close(filedes) fakeLogClose(filedes)
#else
#define log_open(pathname, flags) open(pathname, flags)
#define log_writev(filedes, vector, count) writev(filedes, vector, count)
#define log_close(filedes) close(filedes)
#endif
~~~
这里,我们看到,一般情况下,log_writev就是writev了,这是个常见的批量文件写入函数,就不多说了。
至些,整个调用过程就结束了。总结一下,首先是从应用程序层调用应用程序框架层的Java接口,应用程序框架层的Java接口通过调用本层的JNI方法进入到系统运行库层的C接口,系统运行库层的C接口通过设备文件来访问内核空间层的Logger驱动程序。这是一个典型的调用过程,很好地诠释Android的系统架构,希望读者好好领会。
- 前言
- Android组件设计思想
- Android源代码开发和调试环境搭建
- Android源代码下载和编译
- Android源代码情景分析法
- Android源代码调试分析法
- 手把手教你为手机编译ROM
- 在Ubuntu上下载、编译和安装Android最新源代码
- 在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)
- 如何单独编译Android源代码中的模块
- 在Ubuntu上为Android系统编写Linux内核驱动程序
- 在Ubuntu上为Android系统内置C可执行程序测试Linux内核驱动程序
- 在Ubuntu上为Android增加硬件抽象层(HAL)模块访问Linux内核驱动程序
- 在Ubuntu为Android硬件抽象层(HAL)模块编写JNI方法提供Java访问硬件服务接口
- 在Ubuntu上为Android系统的Application Frameworks层增加硬件访问服务
- 在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务
- Android源代码仓库及其管理工具Repo分析
- Android编译系统简要介绍和学习计划
- Android编译系统环境初始化过程分析
- Android源代码编译命令m/mm/mmm/make分析
- Android系统镜像文件的打包过程分析
- 从CM刷机过程和原理分析Android系统结构
- Android系统架构概述
- Android系统整体架构
- android专用驱动
- Android硬件抽象层HAL
- Android应用程序组件
- Android应用程序框架
- Android用户界面架构
- Android虚拟机之Dalvik虚拟机
- Android硬件抽象层
- Android硬件抽象层(HAL)概要介绍和学习计划
- Android专用驱动
- Android Logger驱动系统
- Android日志系统驱动程序Logger源代码分析
- Android应用程序框架层和系统运行库层日志系统源代码分析
- Android日志系统Logcat源代码简要分析
- Android Binder驱动系统
- Android进程间通信(IPC)机制Binder简要介绍和学习计划
- 浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路
- 浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路
- Android系统进程间通信(IPC)机制Binder中的Server启动过程源代码分析
- Android系统进程间通信(IPC)机制Binder中的Client获得Server远程接口过程源代码分析
- Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析
- Android Ashmem驱动系统
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析
- Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析
- Android系统匿名共享内存(Anonymous Shared Memory)C++调用接口分析
- Android应用程序进程管理
- Android应用程序进程启动过程的源代码分析
- Android系统进程Zygote启动过程的源代码分析
- Android系统默认Home应用程序(Launcher)的启动过程源代码分析
- Android应用程序消息机制
- Android应用程序消息处理机制(Looper、Handler)分析
- Android应用程序线程消息循环模型分析
- Android应用程序输入事件分发和处理机制
- Android应用程序键盘(Keyboard)消息处理机制分析
- Android应用程序UI架构
- Android系统的开机画面显示过程分析
- Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析
- SurfaceFlinger
- Android系统Surface机制的SurfaceFlinger服务
- SurfaceFlinger服务简要介绍和学习计划
- 启动过程分析
- 对帧缓冲区(Frame Buffer)的管理分析
- 线程模型分析
- 渲染应用程序UI的过程分析
- Android应用程序与SurfaceFlinger服务的关系
- 概述和学习计划
- 连接过程分析
- 共享UI元数据(SharedClient)的创建过程分析
- 创建Surface的过程分析
- 渲染Surface的过程分析
- Android应用程序窗口(Activity)
- 实现框架简要介绍和学习计划
- 运行上下文环境(Context)的创建过程分析
- 窗口对象(Window)的创建过程分析
- 视图对象(View)的创建过程分析
- 与WindowManagerService服务的连接过程分析
- 绘图表面(Surface)的创建过程分析
- 测量(Measure)、布局(Layout)和绘制(Draw)过程分析
- WindowManagerService
- WindowManagerService的简要介绍和学习计划
- 计算Activity窗口大小的过程分析
- 对窗口的组织方式分析
- 对输入法窗口(Input Method Window)的管理分析
- 对壁纸窗口(Wallpaper Window)的管理分析
- 计算窗口Z轴位置的过程分析
- 显示Activity组件的启动窗口(Starting Window)的过程分析
- 切换Activity窗口(App Transition)的过程分析
- 显示窗口动画的原理分析
- Android控件TextView的实现原理分析
- Android视图SurfaceView的实现原理分析
- Android应用程序UI硬件加速渲染
- 简要介绍和学习计划
- 环境初始化过程分析
- 预加载资源地图集服务(Asset Atlas Service)分析
- Display List构建过程分析
- Display List渲染过程分析
- 动画执行过程分析
- Android应用程序资源管理框架
- Android资源管理框架(Asset Manager)
- Asset Manager 简要介绍和学习计划
- 编译和打包过程分析
- Asset Manager的创建过程分析
- 查找过程分析
- Dalvik虚拟机和ART虚拟机
- Dalvik虚拟机
- Dalvik虚拟机简要介绍和学习计划
- Dalvik虚拟机的启动过程分析
- Dalvik虚拟机的运行过程分析
- Dalvik虚拟机JNI方法的注册过程分析
- Dalvik虚拟机进程和线程的创建过程分析
- Dalvik虚拟机垃圾收集机制简要介绍和学习计划
- Dalvik虚拟机Java堆创建过程分析
- Dalvik虚拟机为新创建对象分配内存的过程分析
- Dalvik虚拟机垃圾收集(GC)过程分析
- ART虚拟机
- Android ART运行时无缝替换Dalvik虚拟机的过程分析
- Android运行时ART简要介绍和学习计划
- Android运行时ART加载OAT文件的过程分析
- Android运行时ART加载类和方法的过程分析
- Android运行时ART执行类方法的过程分析
- ART运行时垃圾收集机制简要介绍和学习计划
- ART运行时Java堆创建过程分析
- ART运行时为新创建对象分配内存的过程分析
- ART运行时垃圾收集(GC)过程分析
- ART运行时Compacting GC简要介绍和学习计划
- ART运行时Compacting GC堆创建过程分析
- ART运行时Compacting GC为新创建对象分配内存的过程分析
- ART运行时Semi-Space(SS)和Generational Semi-Space(GSS)GC执行过程分析
- ART运行时Mark-Compact( MC)GC执行过程分析
- ART运行时Foreground GC和Background GC切换过程分析
- Android安全机制
- SEAndroid安全机制简要介绍和学习计划
- SEAndroid安全机制框架分析
- SEAndroid安全机制中的文件安全上下文关联分析
- SEAndroid安全机制中的进程安全上下文关联分析
- SEAndroid安全机制对Android属性访问的保护分析
- SEAndroid安全机制对Binder IPC的保护分析
- 从NDK在非Root手机上的调试原理探讨Android的安全机制
- APK防反编译
- Android视频硬解稳定性问题探讨和处理
- Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析
- Android应用程序安装过程源代码分析
- Android应用程序启动过程源代码分析
- 四大组件源代码分析
- Activity
- Android应用程序的Activity启动过程简要介绍和学习计划
- Android应用程序内部启动Activity过程(startActivity)的源代码分析
- 解开Android应用程序组件Activity的"singleTask"之谜
- Android应用程序在新的进程中启动新的Activity的方法和过程分析
- Service
- Android应用程序绑定服务(bindService)的过程源代码分析
- ContentProvider
- Android应用程序组件Content Provider简要介绍和学习计划
- Android应用程序组件Content Provider应用实例
- Android应用程序组件Content Provider的启动过程源代码分析
- Android应用程序组件Content Provider在应用程序之间共享数据的原理分析
- Android应用程序组件Content Provider的共享数据更新通知机制分析
- BroadcastReceiver
- Android系统中的广播(Broadcast)机制简要介绍和学习计划
- Android应用程序注册广播接收器(registerReceiver)的过程分析
- Android应用程序发送广播(sendBroadcast)的过程分析