Android系统 自动加载自定义JAR文件

简介: Android系统 自动加载自定义JAR文件

Android操作系统是一个复杂的生态系统,有时我们可能需要在系统级别添加自定义的Java类库。本文将指导你如何在Android中系统级加载自定义的JAR文件。

1. 问题描述

我是新增了个Framework binder demo ,这个demo包含(ClientDemoServerDemo)可执行文件 ,需要依赖(/system/framework/ClientDemo.jar:/system/framework/ServerDemo.jar)当我尝试在Android系统中直接启动可执行文件时,遇到以下错误:

ClassLoader referenced unknown path: /system/framework/ClientDemo.jar
2023-09-13 15:53:04.226 10765-10765 appproc                 pid-10765                            E  ERROR: could not find class 'com.ln28.frameworkBinder.ClientDemo'
2023-09-13 15:53:04.226 10765-10765 app_process             pid-10765                            A  thread.cc:2361] No pending exception expected: java.lang.ClassNotFoundException: com.ln28.frameworkBinder.ClientDemo
                                                                                                    thread.cc:2361] (Throwable with empty stack trace)
.............

这意味着Android系统在启动时没有加载我们的JAR文件?

2. 解决方案

2.1 完整调试过程

问题找到了,我想尝试反编译这个JAR确认文件是否正常 ,使用平常的反编译工具打开

然后查阅资料发现 ClientDemo.jar文件包含一个classes.dex,那么它实际上是一个DEX文件,不是一个常规的Java JAR文件。DEX文件是Android特有的,它包含为Dalvik或ART运行时优化的字节码。

要查看DEX文件中的类,需要使用到dexdump工具,使用它来列出DEX文件中的所有类:

rk3568_r:/ # dexdump -l plain /system/framework/ClientDemo.jar
Processing '/system/framework/ClientDemo.jar'...
Opened '/system/framework/ClientDemo.jar', DEX version '039'
Class #0            -
  Class descriptor  : 'Lcom/ln28/frameworkBinder/ClientDemo;'
  Access flags      : 0x0001 (PUBLIC)
  Superclass        : 'Ljava/lang/Object;'
  Interfaces        -
  Static fields     -
  Instance fields   -
  Direct methods    -
    #0              : (in Lcom/ln28/frameworkBinder/ClientDemo;)
      name          : '<init>'
      type          : '()V'
      access        : 0x10001 (PUBLIC CONSTRUCTOR)
      code          -
      registers     : 1
      ins           : 1
      outs          : 1
      insns size    : 4 16-bit code units
      catches       : (none)
      positions     :
        0x0000 line=7
      locals        :
........
Class #1            -
  Class descriptor  : 'Lcom/ln28/frameworkBinder/IMyService;'
  Access flags      : 0x0601 (PUBLIC INTERFACE ABSTRACT)
  Superclass        : 'Ljava/lang/Object;'
  Interfaces        -
........

这将列出DEX文件中的所有类和方法。

如果确定 .jar 文件是正确的,并且类确实存在,那么问题可能是与Android的类加载器或应用程序的构建和部署过程有关。

因为这个类是自定义的 怀疑可能没有被系统加载 那么该如何确认呢?

在Android系统启动时,BOOTCLASSPATH环境变量定义了系统类加载器应该加载哪些JAR文件。知道是环境变量就好办了 还好有些Linux基础 , 通过基操读出BOOTCLASSPATH环境变量 看到一堆jar定义 好家伙总算找到突破口了

rk3568_r:/ # echo $BOOTCLASSPATH
/apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/core-icu4j.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/framework-atb-backward-compatibility.jar:/apex/com.android.conscrypt/javalib/conscrypt.jar:/apex/com.android.media/javalib/updatable-media.jar:/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar:/apex/com.android.os.statsd/javalib/framework-statsd.jar:/apex/com.android.permission/javalib/framework-permission.jar:/apex/com.android.sdkext/javalib/framework-sdkextensions.jar:/apex/com.android.wifi/javalib/framework-wifi.jar:/apex/com.android.tethering/javalib/framework-tethering.jar

目标就是要确保Android系统可以加载和使用自定义的JAR文件,需要将其添加到BOOTCLASSPATH环境变量中。

那么该怎么在原由基础上设置环境变量呢?

还是Linux 的基操 ,执行export后再读取发现已经添加进去了

export BOOTCLASSPATH=$BOOTCLASSPATH:/system/framework/ClientDemo.jar
rk3568_r:/ # echo $BOOTCLASSPATH
...................省略 :/system/framework/ClientDemo.jar

那么问题来了,这种设置环境变量的方式是临时的 可是我要开机的时候就系统级别的加载和system/framework下面的jar一样 怎么搞?

我在系统源码中搜遍了 grep -rn "BOOTCLASSPATH" 只在system目录下发现了

core/rootdir/Android.mk:159:    $(hide) sed -e 's?%BOOTCLASSPATH%?$(PRODUCT_BOOTCLASSPATH)?g' $< >$@
core/rootdir/Android.mk:160:    $(hide) sed -i -e 's?%DEX2OATBOOTCLASSPATH%?$(PRODUCT_DEX2OAT_BOOTCLASSPATH)?g' $@
core/rootdir/init.environ.rc.in:13:    export BOOTCLASSPATH %BOOTCLASSPATH%
core/rootdir/init.environ.rc.in:14:    export DEX2OATBOOTCLASSPATH %DEX2OATBOOTCLASSPATH%

其中system/core/rootdir/init.environ.rc.in文件,这不就是我需要找的内容嘛

# set up the global environment
on early-init
    export ANDROID_BOOTLOGO 1
    export ANDROID_ROOT /system
    export ANDROID_ASSETS /system/app
    export ANDROID_DATA /data
    export ANDROID_STORAGE /storage
    export ANDROID_ART_ROOT /apex/com.android.art
    export ANDROID_I18N_ROOT /apex/com.android.i18n
    export ANDROID_TZDATA_ROOT /apex/com.android.tzdata
    export EXTERNAL_STORAGE /sdcard
    export ASEC_MOUNTPOINT /mnt/asec
    export BOOTCLASSPATH %BOOTCLASSPATH%
    export DEX2OATBOOTCLASSPATH %DEX2OATBOOTCLASSPATH%
    export SYSTEMSERVERCLASSPATH %SYSTEMSERVERCLASSPATH%
    %EXPORT_GLOBAL_ASAN_OPTIONS%
    %EXPORT_GLOBAL_GCOV_OPTIONS%
    %EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS%
    %EXPORT_GLOBAL_HWASAN_OPTIONS%

我想尽快验证我的想法 懒得改代码重新编译 还好在之前的文章( Android系统 自定义动态修改init.custom.rc)中做了自定义可以直接pull/push 马上验证。

adb pull /vendor/etc/init/hw/init.custom.rc
-----------
on post-fs-data
  export BOOTCLASSPATH=$BOOTCLASSPATH:/system/framework/ClientDemo.jar
-----------
adb push init.custom.rc /vendor/etc/init/hw/init.custom.rc

重启发现还是没有设置成功 ? 这…

然后发现 在init.rc脚本中,export命令的行为与标准的Unix shell略有不同。

搜索发现系统源码中都是直接设置整个值,而不是追加新的值。

2.1. 将JAR文件放在适当的位置

首先确保自定义JAR文件(例如ClientDemo.jar)已经被放置在/system/framework/或其他适当的目录中。在mk文件中include $(BUILD_JAVA_LIBRARY)/system/framework路径。

2.2. 修改启动脚本

BOOTCLASSPATH通常在init.rc或特定于设备的init.*.rc文件中定义。为了添加自定义的JAR文件系统级加载,需要修改这些文件。

init.custom.rc或设备特定的init.*.rc文件的post-fs-data阶段,添加以下内容:

on post-fs-data
    mkdir /mnt/custom3 0755 root root
    export BOOTCLASSPATH /apex/com.android.art/javalib/core-oj.jar:/apex/com.android.art/javalib/core-libart.jar:/apex/com.android.art/javalib/core-icu4j.jar:/apex/com.android.art/javalib/okhttp.jar:/apex/com.android.art/javalib/bouncycastle.jar:/apex/com.android.art/javalib/apache-xml.jar:/system/framework/framework.jar:/system/framework/ext.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/framework-atb-backward-compatibility.jar:/apex/com.android.conscrypt/javalib/conscrypt.jar:/apex/com.android.media/javalib/updatable-media.jar:/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar:/apex/com.android.os.statsd/javalib/framework-statsd.jar:/apex/com.android.permission/javalib/framework-permission.jar:/apex/com.android.sdkext/javalib/framework-sdkextensions.jar:/apex/com.android.wifi/javalib/framework-wifi.jar:/apex/com.android.tethering/javalib/framework-tethering.jar:/system/framework/ClientDemo.jar:/system/framework/ServerDemo.jar

所以需要完整地列出了所有的BOOTCLASSPATH路径,包括新的和原始的,然后使用export命令一次性设置它。

2.3. 重新编译和刷入系统

如果不想每次手动 那必须改源代码了呀 需要对init.custom.rc或设备特定的init脚本进行了修改后,需要重新编译Android系统并刷入到设备上。

2.4. 验证

烧完设备重新启动,可以使用以下命令来检查BOOTCLASSPATH是否已经更新:

echo $BOOTCLASSPATH

确保/system/framework/ClientDemo.jar出现在输出中。

3.Android系统类加载流程

以下是学习的内容~

BOOTCLASSPATH是Android系统中一个非常重要的环境变量,它定义了系统启动时Java类加载器应该加载哪些核心库。这些库包括Android框架的核心部分,以及其他关键的Java库。当Zygote进程启动时,它会使用BOOTCLASSPATH来预加载这些类库,这样应用程序在启动时就可以快速地访问这些类。

BOOTCLASSPATH在Android系统中的加载流程:

  1. 系统启动

当Android设备启动时,init进程是第一个被启动的进程。init进程负责初始化系统,启动各种服务,并设置各种环境变量,其中之一就是BOOTCLASSPATH。可以查看system/core/rootdir/init.environ.rc.in文件。

  1. 设置BOOTCLASSPATH

init.rc或特定于设备的init.*.rc文件中,BOOTCLASSPATH被设置为一个包含多个JAR文件路径的字符串。这些JAR文件包含了Android系统运行所需的所有Java类。

  1. Zygote进程启动

init进程在完成其初始化任务后会启动Zygote进程。Zygote是Android中所有应用进程的父进程,它预加载所有在BOOTCLASSPATH中定义的类库,这样当新的应用进程启动时,它们可以快速地访问这些预加载的类。

  1. 类预加载

Zygote进程使用BOOTCLASSPATH中定义的路径来预加载类。这意味着它会读取每个JAR文件,并加载其中的所有Java类。这个预加载过程确保了应用程序在启动时可以快速地访问这些类,而不需要从磁盘上重新加载它们。

  1. 应用进程启动
    当你启动一个应用程序时,一个新的进程会从Zygote进程fork出来。由于Zygote已经预加载了所有的核心类,所以新的应用进程可以立即访问这些类,而不需要等待。
  2. 应用程序使用类库

应用程序在运行时会使用许多核心类库,这些类库已经被预加载到Zygote进程中,所以应用程序可以快速地访问它们。

BOOTCLASSPATH在Android系统的启动过程中的作用:

它定义了系统应该加载哪些核心Java类库,确保应用程序在启动时可以快速地访问这些类。


希望这篇博客能帮助你理解如何在Android系统中加载自定义的JAR文件。如果有任何其他问题,请留言。

相关文章
|
2月前
|
存储 Shell Android开发
基于Android P,自定义Android开机动画的方法
本文详细介绍了基于Android P系统自定义开机动画的步骤,包括动画文件结构、脚本编写、ZIP打包方法以及如何将自定义动画集成到AOSP源码中。
48 2
基于Android P,自定义Android开机动画的方法
|
2月前
|
供应链 物联网 区块链
未来触手可及:探索新兴技术的趋势与应用安卓开发中的自定义视图:从基础到进阶
【8月更文挑战第30天】随着科技的飞速发展,新兴技术如区块链、物联网和虚拟现实正在重塑我们的世界。本文将深入探讨这些技术的发展趋势和应用场景,带你领略未来的可能性。
|
2月前
|
Java Android开发 C++
Android Studio JNI 使用模板:c/cpp源文件的集成编译,快速上手
本文提供了一个Android Studio中JNI使用的模板,包括创建C/C++源文件、编辑CMakeLists.txt、编写JNI接口代码、配置build.gradle以及编译生成.so库的详细步骤,以帮助开发者快速上手Android平台的JNI开发和编译过程。
93 1
|
2月前
|
测试技术 Android开发 Python
探索软件测试的艺术:从基础到高级安卓应用开发中的自定义视图
【8月更文挑战第29天】在软件开发的世界中,测试是不可或缺的一环。它如同艺术一般,需要精细的技巧和深厚的知识。本文旨在通过浅显易懂的语言,引领读者从软件测试的基础出发,逐步深入到更复杂的测试策略和工具的使用,最终达到能够独立进行高效测试的水平。我们将一起探索如何通过不同的测试方法来确保软件的质量和性能,就像艺术家通过不同的色彩和笔触来完成一幅画作一样。
|
2月前
|
JavaScript 前端开发 Java
[Android][Framework]系统jar包,sdk的制作及引用
[Android][Framework]系统jar包,sdk的制作及引用
43 0
|
16天前
|
Android开发 开发者
安卓开发中的自定义视图:从入门到精通
【9月更文挑战第19天】在安卓开发的广阔天地中,自定义视图是一块充满魔力的土地。它不仅仅是代码的堆砌,更是艺术与科技的完美结合。通过掌握自定义视图,开发者能够打破常规,创造出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战应用,一步步展示如何用代码绘出心中的蓝图。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往创意和效率的大门。让我们一起探索自定义视图的秘密,将你的应用打造成一件艺术品吧!
41 10
|
21天前
|
XML 编解码 Android开发
安卓开发中的自定义视图控件
【9月更文挑战第14天】在安卓开发中,自定义视图控件是一种高级技巧,它可以让开发者根据项目需求创建出独特的用户界面元素。本文将通过一个简单示例,引导你了解如何在安卓项目中实现自定义视图控件,包括创建自定义控件类、处理绘制逻辑以及响应用户交互。无论你是初学者还是有经验的开发者,这篇文章都会为你提供有价值的见解和技巧。
30 3
|
22天前
|
前端开发 Android开发 开发者
安卓应用开发中的自定义视图基础
【9月更文挑战第13天】在安卓开发的广阔天地中,自定义视图是一块神奇的画布,它允许开发者将想象力转化为用户界面的创新元素。本文将带你一探究竟,了解如何从零开始构建自定义视图,包括绘图基础、触摸事件处理,以及性能优化的实用技巧。无论你是想提升应用的视觉吸引力,还是追求更流畅的交互体验,这里都有你需要的金钥匙。
|
25天前
|
缓存 搜索推荐 Android开发
安卓应用开发中的自定义View组件实践
【9月更文挑战第10天】在安卓开发领域,自定义View是提升用户体验和实现界面个性化的重要手段。本文将通过一个实际案例,展示如何在安卓项目中创建和使用自定义View组件,包括设计思路、实现步骤以及可能遇到的问题和解决方案。文章不仅提供了代码示例,还深入探讨了自定义View的性能优化技巧,旨在帮助开发者更好地掌握这一技能。
|
2月前
|
XML 搜索推荐 Android开发
安卓开发中的自定义View组件实践
【8月更文挑战第30天】探索Android世界,自定义View是提升应用界面的关键。本文以简洁的语言带你了解如何创建自定义View,从基础到高级技巧,一步步打造个性化的UI组件。