深入理解android卷1—java调用native代码


最近在阅读深入理解android卷1,把一些重要的思路记录下来,当然是书中没有细述的一些东西,便于后续翻阅。该书中jni是借助media相关代码展开。

frameworks中关于media的代码

build/core/pathmap.mk中hard code了frameworks的代码路径,其中media相关代码在frameworks/base/media下,

FRAMEWORKS_BASE_SUBDIRS := \
    $(addsuffix /java, \
        core \
        graphics \
        location \
        media \
        opengl \
        sax \
        telephony \
        wifi \
        vpn \
        keystore \
        voip \
     )

#
# A version of FRAMEWORKS_BASE_SUBDIRS that is expanded to full paths from
# the root of the tree.  This currently needs to be here so that other libraries
# and apps can find the .aidl files in the framework, though we should really
# figure out a better way to do this.
#
FRAMEWORKS_BASE_JAVA_SRC_DIRS := \
    $(addprefix frameworks/base/,$(FRAMEWORKS_BASE_SUBDIRS))

java和jni函数对应关系

在java代码中,如果调用的函数有native修饰,则是要调用native函数,在framworks/base/media/java/android/media/MediaScanner.java中,

private static native final void native_init();

则native_init即是native函数实现的,如何将java中函数和native对应起来?
例如,MediaScanner.java中native_init函数,全路径为android.media.MediaScanner.native_init函数,则native的函数名为将.换为下划线,即android_media_MediaScanner_native_init()但是也不是一定的,如果找不到,一般对应的Native函数一般都在形如“包名_类名”的cpp或者c中,即android_media_MediaScanner.cpp。

此外,找JNI层对应函数时,还可以通过搜索代码 包名/类名 的方式,例如MediaScanner.java中有native函数,包名为android/media,则可以搜索android/media/MediaScanner,因为在JNI函数动态注册的时候会调用AndroidRuntime::registerNativeMethods(),这个函数的第二个参数为形如android/media/MediaScanner的类名,表明这是类android/media/MediaScanner中调用的native函数。

System.loadLibrary

在framworks/base/media/java/android/media/MediaScanner.java类中,调用System.loadLibrary来加载libmedia_jni.so

public class MediaScanner
{
    static {
        System.loadLibrary("media_jni");
        native_init();
    }
    .............
}

那么需要关注两个部分,一个就是System.loadLibrary函数,另一个就是media_jni库,当然在android中库的全称为libmedia_jni.so,先介绍System.loadLibrary,
在libcore\luni\src\main\java\java\lang\System.java中,

    public static void loadLibrary(String libName) {
        SecurityManager smngr = System.getSecurityManager();
        if (smngr != null) {
            smngr.checkLink(libName);
        }
        Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
    }

其实是调用了Runtime中的loadLibrary,在libcore\luni\src\main\java\java\lang\Runtime.java中,

 /*
     * Loads and links a library without security checks.
     */
    void loadLibrary(String libraryName, ClassLoader loader) {
        if (loader != null) {
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
                throw new UnsatisfiedLinkError("Couldn't load " + libraryName + ": " +
                        "findLibrary returned null");
            }
            String error = nativeLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }

        String filename = System.mapLibraryName(libraryName);
        List<String> candidates = new ArrayList<String>();
        String lastError = null;
        for (String directory : mLibPaths) {
            String candidate = directory + filename;
            candidates.add(candidate);
            if (new File(candidate).exists()) {
                String error = nativeLoad(candidate, loader);
                if (error == null) {
                    return; // We successfully loaded the library. Job done.
                }
                lastError = error;
            }
        }

        if (lastError != null) {
            throw new UnsatisfiedLinkError(lastError);
        }
        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    }

都会调用nativeLoad(filename, loader)函数,去完成库的加载,而libcore\luni\src\main\java\java\lang\Runtime.java中,nativeLoad是一个native函数,

private static native String nativeLoad(String filename, ClassLoader loader);

那么我们就要去找native函数,按照前面的方法,nativeLoad的native函数应该名为java_lang_Runtime_nativeLoad()函数,但是没找到。所以又去java_lang_Runtime.c或者cpp中找,最终在java_lang_Runtime.c找到了。

 const DalvikNativeMethod dvm_java_lang_Runtime[] = {
    { "freeMemory",          "()J",
        Dalvik_java_lang_Runtime_freeMemory },
    { "gc",                 "()V",
        Dalvik_java_lang_Runtime_gc },
    { "availableProcessors", "()I",
        Dalvik_java_lang_Runtime_availableProcessors },
    { "maxMemory",          "()J",
        Dalvik_java_lang_Runtime_maxMemory },
    { "nativeExit",         "(IZ)V",
        Dalvik_java_lang_Runtime_nativeExit },
    { "nativeLoad",         "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/String;",
        Dalvik_java_lang_Runtime_nativeLoad },
    { "runFinalization",    "(Z)V",
        Dalvik_java_lang_Runtime_runFinalization },
    { "totalMemory",          "()J",
        Dalvik_java_lang_Runtime_totalMemory },
    { NULL, NULL, NULL },
    };

既是继续调用Dalvik_java_lang_Runtime_nativeLoad函数,

static void Dalvik_java_lang_Runtime_nativeLoad(const u4* args,
    JValue* pResult)
{
    StringObject* fileNameObj = (StringObject*) args[0];
    Object* classLoader = (Object*) args[1];
    char* fileName = NULL;
    StringObject* result = NULL;
    char* reason = NULL;
    bool success;

    assert(fileNameObj != NULL);
    fileName = dvmCreateCstrFromString(fileNameObj);

    success = dvmLoadNativeCode(fileName, classLoader, &reason);
    if (!success) {
        const char* msg = (reason != NULL) ? reason : "unknown failure";
        result = dvmCreateStringFromCstr(msg);
        dvmReleaseTrackedAlloc((Object*) result, NULL);
    }

    free(reason);
    free(fileName);
    RETURN_PTR(result);
}

继续调用dvmLoadNativeCode(),

bool dvmLoadNativeCode(const char* pathName, Object* classLoader,
        char** detail)
{
    SharedLib* pEntry;
    void* handle;
    bool verbose;

    /* reduce noise by not chattering about system libraries */
    //如果不是以/system或者/vendor开头
    verbose = !!strncmp(pathName, "/system", sizeof("/system")-1);
    verbose = verbose && !!strncmp(pathName, "/vendor", sizeof("/vendor")-1);

    if (verbose)
        LOGD("Trying to load lib %s %p\n", pathName, classLoader);

    *detail = NULL;

    /*
     * See if we've already loaded it.  If we have, and the class loader
     * matches, return successfully without doing anything.
     */
    pEntry = findSharedLibEntry(pathName);
    if (pEntry != NULL) {
        if (pEntry->classLoader != classLoader) {
            LOGW("Shared lib '%s' already opened by CL %p; can't open in %p\n",
                pathName, pEntry->classLoader, classLoader);
            return false;
        }
        if (verbose) {
            LOGD("Shared lib '%s' already loaded in same CL %p\n",
                pathName, classLoader);
        }
        if (!checkOnLoadResult(pEntry))
            return false;
        return true;
    }

    /*
     * Open the shared library.  Because we're using a full path, the system
     * doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
     * resolve this library's dependencies though.)
     *
     * Failures here are expected when java.library.path has several entries
     * and we have to hunt for the lib.
     *
     * The current version of the dynamic linker prints detailed information
     * about dlopen() failures.  Some things to check if the message is
     * cryptic:
     *   - make sure the library exists on the device
     *   - verify that the right path is being opened (the debug log message
     *     above can help with that)
     *   - check to see if the library is valid (e.g. not zero bytes long)
     *   - check config/prelink-linux-arm.map to ensure that the library
     *     is listed and is not being overrun by the previous entry (if
     *     loading suddenly stops working on a prelinked library, this is
     *     a good one to check)
     *   - write a trivial app that calls sleep() then dlopen(), attach
     *     to it with "strace -p <pid>" while it sleeps, and watch for
     *     attempts to open nonexistent dependent shared libs
     *
     * This can execute slowly for a large library on a busy system, so we
     * want to switch from RUNNING to VMWAIT while it executes.  This allows
     * the GC to ignore us.
     */
    Thread* self = dvmThreadSelf();
    ThreadStatus oldStatus = dvmChangeStatus(self, THREAD_VMWAIT);
    //打开库
    handle = dlopen(pathName, RTLD_LAZY);
    dvmChangeStatus(self, oldStatus);

    if (handle == NULL) {
        *detail = strdup(dlerror());
        return false;
    }

    /* create a new entry */
    SharedLib* pNewEntry;
    pNewEntry = (SharedLib*) calloc(1, sizeof(SharedLib));
    pNewEntry->pathName = strdup(pathName);
    pNewEntry->handle = handle;
    pNewEntry->classLoader = classLoader;
    dvmInitMutex(&pNewEntry->onLoadLock);
    pthread_cond_init(&pNewEntry->onLoadCond, NULL);
    pNewEntry->onLoadThreadId = self->threadId;

    /* try to add it to the list */
    SharedLib* pActualEntry = addSharedLibEntry(pNewEntry);

    if (pNewEntry != pActualEntry) {
        LOGI("WOW: we lost a race to add a shared lib (%s CL=%p)\n",
            pathName, classLoader);
        freeSharedLibEntry(pNewEntry);
        return checkOnLoadResult(pActualEntry);
    } else {
        if (verbose)
            LOGD("Added shared lib %s %p\n", pathName, classLoader);

        bool result = true;
        void* vonLoad;
        int version;
    //在库中找到JNI_OnLoad符号,即函数
        vonLoad = dlsym(handle, "JNI_OnLoad");
        if (vonLoad == NULL) {
            LOGD("No JNI_OnLoad found in %s %p, skipping init\n",
                pathName, classLoader);
        } else {
            /*
             * Call JNI_OnLoad.  We have to override the current class
             * loader, which will always be "null" since the stuff at the
             * top of the stack is around Runtime.loadLibrary().  (See
             * the comments in the JNI FindClass function.)
             */
            OnLoadFunc func = vonLoad;
            Object* prevOverride = self->classLoaderOverride;

            self->classLoaderOverride = classLoader;
            oldStatus = dvmChangeStatus(self, THREAD_NATIVE);
            LOGV("+++ calling JNI_OnLoad(%s)\n", pathName);
            //执行JNI_OnLoad函数
            version = (*func)(gDvm.vmList, NULL);
            dvmChangeStatus(self, oldStatus);
            self->classLoaderOverride = prevOverride;

            if (version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 &&
                version != JNI_VERSION_1_6)
            {
                LOGW("JNI_OnLoad returned bad version (%d) in %s %p\n",
                    version, pathName, classLoader);
                /*
                 * It's unwise to call dlclose() here, but we can mark it
                 * as bad and ensure that future load attempts will fail.
                 *
                 * We don't know how far JNI_OnLoad got, so there could
                 * be some partially-initialized stuff accessible through
                 * newly-registered native method calls.  We could try to
                 * unregister them, but that doesn't seem worthwhile.
                 */
                result = false;
            } else {
                LOGV("+++ finished JNI_OnLoad %s\n", pathName);
            }
        }

        if (result)
            pNewEntry->onLoadResult = kOnLoadOkay;
        else
            pNewEntry->onLoadResult = kOnLoadFailed;

        pNewEntry->onLoadThreadId = 0;

        /*
         * Broadcast a wakeup to anybody sleeping on the condition variable.
         */
        dvmLockMutex(&pNewEntry->onLoadLock);
        pthread_cond_broadcast(&pNewEntry->onLoadCond);
        dvmUnlockMutex(&pNewEntry->onLoadLock);
        return result;
    }
}

那么从上面的代码我们能看出,System.loadLibrary("media_jni")最终会去调用库libmedia_jni.so中的JNI_OnLoad()函数。

libmedia_jni.so

如何找这个库呢?当然是从makefile中入手,在makefile中搜索media_jni,
即,

LOCAL_MODULE:= libmedia_jni
include $(BUILD_SHARED_LIBRARY)

前面在将介绍build/envsetup.sh中讲到,这个脚本提供了很多便捷的搜索函数,例如cgrep等,我增加了一个在makefile中搜索的函数,找相关模块非常方便。在envsetup.sh中模仿jgrep,

function mkgrep()
{
    find . -type f -name "*\.mk" -print0 | xargs -0 grep --color -n "$@"
}

因为media的相关代码都在frameworks\base\media下,所以很容易搜索到,

$ mkgrep media_jni
./build/core/user_tags.mk:242:  libmedia_jni \
./build/CleanSpec.mk:48:$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libmedia_jni_intermediates)
./frameworks/base/media/jni/Android.mk:45:LOCAL_MODULE:= libmedia_jni

在LOCAL_SRC_FILES中搜索,找到JNI_OnLoad就在frameworks\base\media\jni\android_media_MediaPlayer.cpp中,

LOCAL_SRC_FILES:= \
    android_media_MediaPlayer.cpp \
    android_media_MediaRecorder.cpp \
    android_media_MediaScanner.cpp \
    android_media_MediaMetadataRetriever.cpp \
    android_media_ResampleInputStream.cpp \
    android_media_MediaProfiles.cpp \
    android_media_AmrInputStream.cpp

关于JNI_OnLoad()函数中,JNI函数的动态注册这里不写了,书中很详细。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
深入理解Android 1 不是扫描版的,是全版电子书的,非PDF,可编辑,各种阅览器以打开!包括书签和同步目录! 第1章 阅读前的准备工作 / 1 1.1 系统架构 / 2 1.1.1 Android系统架构 / 2 1.1.2 本书的架构 / 3 1.2 搭建开发环境 / 4 1.2.1 下载源码 / 4 1.2.2 编译源码 / 6 1.3 工具介绍 / 8 1.3.1 Source Insight介绍 / 8 1.3.3 Busybox的使用 / 11 1.4 本章小结 / 12 第2章 深入理解JNI / 13 2.1 JNI概述 / 14 2.2 学习JNI的实例:MediaScanner / 15 2.3 Java层的MediaScanner分析 / 16 2.3.1 加载JNI库 / 16 2.3.2 Javanative函数和总结 / 17 2.4 JNIMediaScanner的分析 / 17 2.4.1 注册JNI函数 / 18 2.4.2 数据类型转换 / 22 2.4.3 JNIEnv介绍 / 24 2.4.4 通过JNIEnv操作jobject / 25 2.4.5 jstring介绍 / 27 2.4.6 JNI类型签名介绍 / 28 2.4.7 垃圾回收 / 29 2.4.8 JNI中的异常处理 / 32 2.5 本章小结 / 32 第3章 深入理解init / 33 3.1 概述 / 34 3.2 init分析 / 34 3.2.1 解析配置文件 / 38 3.2.2 解析service / 42 3.2.3 init控制service / 48 3.2.4 属性服务 / 52 3.3 本章小结 / 60 第4章 深入理解zygote / 61 4.1 概述 / 62 4.2 zygote分析 / 62 4.2.1 AppRuntime分析 / 63 4.2.2 Welcome to Java World / 68 4.2.3 关于zygote的总结 / 74 4.3 SystemServer分析 / 74 4.3.1 SystemServer的诞生 / 74 4.3.2 SystemServer的重要使命 / 77 4.3.3 关于 SystemServer的总结 / 83 4.4 zygote的分裂 / 84 4.4.1 ActivityManagerService发送请求 / 84 4.4.2 有求必应之响应请求 / 86 4.4.3 关于zygote分裂的总结 / 88 4.5 拓展思考 / 88 4.5.1 虚拟机heapsize的限制 / 88 4.5.2 开机速度优化 / 89 4.5.3 Watchdog分析 / 90 4.6 本章小结 / 93 第5章 深入理解常见类 / 95 5.1 概述 / 96 5.2 以“三板斧”揭秘RefBase、sp和wp / 96 5.2.1 第一板斧——初识影子对象 / 96 5.2.2 第二板斧——由弱生强 / 103 5.2.3 第三板斧——破解生死魔咒 / 106 5.2.4 轻量级的引用计数控制类LightRefBase / 108 5.2.5 题外话-三板斧的来历 / 109 5.3 Thread类及常用同步类分析 / 109 5.3.1 一个变量引发的思考 / 109 5.3.2 常用同步类 / 114 5.4 Looper和Handler类分析 / 121 5.4.1 Looper类分析 / 122 5.4.2 Handler分析 / 124 5.4.3 Looper和Handler的同步关系 / 127 5.4.4 HandlerThread介绍 / 129 5.5 本章小结 / 129 第6章 深入理解Binder / 130 6.1 概述 / 131 6.2 庖丁解MediaServer / 132 6.2.1 MediaServer的入口函数 / 132 6.2.2 独一无二的ProcessState / 133 6.2.3 时空穿越魔术-defaultServiceManager / 134 6.2.4 注册MediaPlayerService / 142 6.2.5 秋风扫落叶-StartThread Pool和join Thread Pool分析 / 149 6.2.6 你彻底明白了吗 / 152 6.3 服务总管ServiceManager / 152 6.3.1 ServiceManager的原理 / 152 6.3.2 服务的注册 / 155 6.3.3 ServiceManager存在的意义 / 158 6.4 MediaPlayerService和它的Client / 158 6.4.1 查询ServiceManager / 158 6.4.2 子承父业 / 159 6.5 拓展思考 / 162 6.5.1 Binder和线程的关系 / 162 6.5.2 有人情味的讣告 / 163 6.5.3 匿名Service / 165 6.6 学以致用 / 166 6.6.1 纯Native的Service / 166 6.6.2 扶得起的“阿斗”(aidl) / 169 6.7 本章小结 / 172 第7章 深入理解Audio系统 / 173 7.1 概述 / 174 7.2 AudioTrack的破解 / 174 7.2.1 用例介绍 / 174 7.2.2 AudioTrack(Java空间)分析 / 179 7.2.3 AudioTrack(Native空间)分析 / 188 7.2.4 关于AudioTrack的总结 / 200 7.3 AudioFlinger的破解 / 200 7.3.1 AudioFlinger的诞生 / 200 7.3.2 通过流程分析AudioFlinger / 204 7.3.3 audio_track_cblk_t分析 / 230 7.3.4 关于AudioFlinger的总结 / 234 7.4 AudioPolicyService的破解 / 234 7.4.1 AudioPolicyService的创建 / 235 7.4.2 重回AudioTrack / 245 7.4.3 声音路由切换实例分析 / 251 7.4.4 关于AudioPolicy的总结 / 262 7.5 拓展思考 / 262 7.5.1 DuplicatingThread破解 / 262 7.5.2 题外话 / 270 7.6 本章小结 / 272 第8章 深入理解Surface系统 / 273 8.1 概述 / 275 8.2 一个Activity的显示 / 275 8.2.1 Activity的创建 / 275 8.2.2 Activity的UI绘制 / 294 8.2.3 关于Activity的总结 / 296 8.3 初识Surface / 297 8.3.1 和Surface有关的流程总结 / 297 8.3.2 Surface之乾坤大挪移 / 298 8.3.3 乾坤大挪移的JNI层分析 / 303 8.3.4 Surface和画图 / 307 8.3.5 初识Surface小结 / 309 8.4 深入分析Surface / 310 8.4.1 与Surface相关的基础知识介绍 / 310 8.4.2 SurfaceComposerClient分析 / 315 8.4.3 SurfaceControl分析 / 320 8.4.4 writeToParcel和Surface对象的创建 / 331 8.4.5 lockCanvas和unlockCanvasAndPost分析 / 335 8.4.6 GraphicBuffer介绍 / 344 8.4.7 深入分析Surface的总结 / 353 8.5 SurfaceFlinger分析 / 353 8.5.1 SurfaceFlinger的诞生 / 354 8.5.2 SF工作线程分析 / 359 8.5.3 Transaction分析 / 368 8.5.4 关于SurfaceFlinger的总结 / 376 8.6 拓展思考 / 377 8.6.1 Surface系统的CB对象分析 / 377 8.6.2 ViewRoot的你问我答 / 384 8.6.3 LayerBuffer分析 / 385 8.7 本章小结 / 394 第9章 深入理解Vold和Rild / 395 9.1 概述 / 396 9.2 Vold的原理与机制分析 / 396 9.2.1 Netlink和Uevent介绍 / 397 9.2.2 初识Vold / 399 9.2.3 NetlinkManager模块分析 / 400 9.2.4 VolumeManager模块分析 / 408 9.2.5 CommandListener模块分析 / 414 9.2.6 Vold实例分析 / 417 9.2.7 关于Vold的总结 / 428 9.3 Rild的原理与机制分析 / 428 9.3.1 初识Rild / 430 9.3.2 RIL_startEventLoop分析 / 432 9.3.3 RIL_Init分析 / 437 9.3.4 RIL_register分析 / 444 9.3.5 关于Rild main函数的总结 / 447 9.3.6 Rild实例分析 / 447 9.3.7 关于Rild的总结 / 459 9.4 拓展思考 / 459 9.4.1 嵌入式系统的存储知识介绍 / 459 9.4.2 Rild和Phone的改进探讨 / 462 9.5 本章小结 / 463 第10章 深入理解MediaScanner / 464 10.1 概述 / 465 10.2 android.process.media分析 / 465 10.2.1 MSR模块分析 / 466 10.2.2 MSS模块分析 / 467 10.2.3 android.process.media媒体扫描工作的流程总结 / 471 10.3 MediaScanner分析 / 472 10.3.1 Java层分析 / 472 10.3.2 JNI层分析 / 476 10.3.3 PVMediaScanner分析 / 479 10.3.4 关于MediaScanner的总结 / 485 10.4 拓展思考 / 486 10.4.1 MediaScannerConnection介绍 / 486 10.4.2 我问你答 / 487 10.5 本章小结 / 488
### 回答1: 《深入理解Android1》是一本关于Android系统的深入学习资料,适合有一定编程基础和对Android开发有兴趣的读者。这本书主要介绍了Android系统各个方面的知识,包括应用程序、系统架构、进程与线程、Binder机制等内容。 在这本书中,读者可以了解到Android系统的基本架构和组成部分。首先,介绍了Android的应用组件,包括活动、服务、广播接收器和内容提供器,详细解释了它们的特点和使用方法,帮助读者理解如何开发基于Android的应用程序。接着,作者介绍了Android系统的进程与线程管理,包括进程的创建与销毁、线程的创建与同步等内容,让读者了解到Android系统是如何管理多个应用程序同时运行的。 另外,本书还详细介绍了Android系统的核心技术——Binder机制。Binder机制是Android系统中用于进程间通信的重要技术,通过它,不同进程之间可以实现数据共享和通信。书中详细介绍了Binder机制的原理以及如何在应用程序中使用Binder进行进程间通信,为读者提供了理解和使用Binder的指南。 总的来说,这本《深入理解Android1》是一本很好的学习资料。通过学习这本书,读者可以深入了解Android系统的背后工作原理,掌握Android应用程序的开发技巧和底层实现细节,为进一步学习和开发Android应用奠定了坚实的基础。无论是对于初学者还是已经有一定Android开发经验的人来说,这本书都是一本不可多得的好书。 ### 回答2: 《深入理解Android1》是一本介绍Android系统原理与开发的经典著作。该书的作者是Bill Phillips和Chris Stewart。这本书主要分为四个部分,包括了从Android系统基础知识到高级开发技巧的内容。 第一部分主要介绍了Android系统的架构和组件,包括了应用程序结构、Activity、Fragment、布局和控件等。通过学习这些内容,读者可以对Android系统的基本构建有更深入的了解,为进一步的开发打下坚实的基础。 第二部分重点讲解了Android系统的核心组件,如Service、Content Provider和Broadcast Receiver。通过对这些组件的深入理解,读者可以更好地进行应用程序的开发,实现数据共享、接收系统广播等重要功能。 第三部分围绕Android系统的高级特性展开,包括了多线程和异步处理、通知、权限管理等相关知识。这些知识点对于开发复杂、功能丰富的Android应用至关重要,可以提高应用程序的性能和用户体验。 第四部分将Android开发推向了一个更高的层次,如数据库的使用、网络编程、地图与位置服务等。通过学习这些高级开发技巧,读者可以开发出更加复杂和实用的Android应用。 总之,《深入理解Android1》是一本很全面、深入的Android开发参考书籍。通过学习本书,读者可以更好地理解Android系统的原理,掌握基础和高级的开发技巧,提高自己在Android开发领域的技术水平。无论是初学者还是有经验的开发者,都可以从中受益匪浅。 ### 回答3: 《深入理解android1 pdf》是一本关于深入学习和理解Android开发的书籍,它详细介绍了Android系统的架构、应用程序的开发、组件的使用和调试等内容。 首先,本书详细介绍了Android系统的架构。它解释了Android的四层架构,包括Linux内核层、系统运行库层、应用框架层和应用程序层。读者可以通过学习这些架构了解Android系统的整体结构和每个层级的功能。 其次,本书全面介绍了Android应用程序的开发。它详细解释了Android的开发环境搭建、基本的应用程序结构和组件的使用。读者可以通过阅读本书,学习到如何创建Activity、Service、Broadcast Receiver和Content Provider等Android组件,并搭建并开发自己的应用程序。 此外,本书还对Android应用程序的调试进行了深入的介绍。它解释了如何使用Android调试工具来调试应用程序的代码,包括使用Logcat进行日志输出、使用DDMS进行监视和调试等。读者可以通过这些调试技巧提高自己的开发效率,快速排查和修复程序中的错误。 总之,《深入理解android1 pdf》是一本非常实用和全面的Android开发指南。通过阅读本书,读者可以系统地学习Android开发的基础知识和技巧,提高自己的应用程序开发水平。无论是初学者还是有一定经验的开发者,都可以从这本书中受益匪浅。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值