1. 程式人生 > >Android6.0系統的framework層中加入自己的共享庫服務,在系統預編譯之後,系統啟動提示找不到類的問題

Android6.0系統的framework層中加入自己的共享庫服務,在系統預編譯之後,系統啟動提示找不到類的問題

共享庫服務我們取名為myserver

系統預編譯(預優化):目的是加快系統的啟動時間,如下設定:

device\atc\evb3561sv_w_no2\BoardConfig.mk
### add by zhaojr for odex
# Enable dex-preoptimization to speed up first boot sequence
ifeq ($(HOST_OS),linux)
  ifeq (user,userdebug $(TARGET_BUILD_VARIANT))
    ifeq ($(WITH_DEXPREOPT),)
      WITH_DEXPREOPT := true
    endif
  endif
endif

編譯之後,下載系統執行提示:

E SystemServer: BOOT FAILURE starting MySecure Service
E SystemServer: java.lang.NoClassDefFoundError: Failed resolution of: Laa/bb/cc/MySecure;
E SystemServer: 	at com.android.server.SystemServer.startOtherServices(SystemServer.java:1034)
E SystemServer: 	at com.android.server.SystemServer.run(SystemServer.java:286)
E SystemServer: 	at com.android.server.SystemServer.main(SystemServer.java:178)
E SystemServer: 	at java.lang.reflect.Method.invoke(Native Method)
E SystemServer: 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:772)
E SystemServer: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:662)
E SystemServer: Caused by: java.lang.ClassNotFoundException: Didn't find class "aa.bb.cc.MySecure" on path: DexPathList[[zip file "/system/framework/services.jar", zip file "/system/framework/ethernet-service.jar", zip file "/system/framework/wifi-service.jar", zip file "/system/framework/pppoe-service.jar"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
E SystemServer: 	at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
E SystemServer: 	at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
E SystemServer: 	at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
E SystemServer: 	... 6 more
E SystemServer: 	Suppressed: java.lang.ClassNotFoundException: Didn't find class "aa.bb.cc.MySecure" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
E SystemServer: 		at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
E SystemServer: 		at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
E SystemServer: 		at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
E SystemServer: 		... 7 more
E SystemServer: 		Suppressed: java.lang.ClassNotFoundException: aa.bb.cc.MySecure
E SystemServer: 			at java.lang.Class.classForName(Native Method)
E SystemServer: 			at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
E SystemServer: 			at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
E SystemServer: 			at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
E SystemServer: 			... 8 more
E SystemServer: 		Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available

如果沒有以上預編譯設定,編譯系統執行是OK。

分析: 開啟Pre-optimization前後編譯差別: 開啟Pre-optimization後,每個模組會額外生成一個.odex檔案,位於/system/framework/oat/arm/目錄; 模組的一些資訊被記錄到boot.art和boot.oat檔案中,位於/system/framework/arm/目錄。 boot.art和boot.oat檔案見後面的參考文章 參考文章提到:boot.art裡面使用的都是絕對地址。 絕對地址! 來,類比幾個概念: 相對路徑<--->絕對路徑 相對地址<--->絕對地址 動態連結<--->靜態連結 LOCAL_JAVA_LIBRARIES<--->LOCAL_STATIC_JAVA_LIBRARIES 基於這個原理,我們可以在Android.mk中設定: LOCAL_JAVA_LIBRARIES := myserver 改為>>LOCAL_STATIC_JAVA_LIBRARIES += myserver 就可以解決上述問題。

但是第三方APP呼叫到myserver 庫仍然存在問題,不可能讓第三方APP使用LOCAL_STATIC_JAVA_LIBRARIES的方式。

基於以上分析我們參考系統中編譯的電話模組就不會出現問題,如下: 那些引用telephony-common.jar的使用的LOCAL_JAVA_LIBRARIES而並沒有出現問題,所以我們可以推測還有其他方式解決類似問題。在build目錄中搜索telephony-common,可以發現在build/target/product/core_minimal.mk檔案PRODUCT_BOOT_JARS的變數中有telephony-common新增,如下:

build/target/product/core_minimal.mk
...........................................
# The order of PRODUCT_BOOT_JARS matters.
PRODUCT_BOOT_JARS := \
    $(TARGET_CORE_JARS) \
    legacy-test \
    ext \
    framework \
    telephony-common \
    voip-common \
    ims-common \
    org.apache.http.legacy.boot \
    android.hidl.base-V1.0-java \
    android.hidl.manager-V1.0-java

  我們參考上面的分析,將我們自己的模組新增進去,如下:

build/target/product/core_minimal.mk
...........................................

# The order of PRODUCT_BOOT_JARS matters.
PRODUCT_BOOT_JARS := \
    $(TARGET_CORE_JARS) \
    legacy-test \
    ext \
    framework \
    telephony-common \
    myserver \
    voip-common \
    ims-common \
    org.apache.http.legacy.boot \
    android.hidl.base-V1.0-java \
    android.hidl.manager-V1.0-java

這就相當於把myserver.jar放到了一個公共的、眾所周知的地方,自然不會出現找不到class的問題.

但是這樣新增之後,編譯會報以下錯誤,如下: unknown package name of class file 用grep查詢錯誤的來源,發現出自一個Python指令碼: build/core/tasks/check_boot_jars/check_boot_jars.py

build\core\tasks\check_boot_jars\check_boot_jars.py
#!/usr/bin/env python

"""
Check boot jars.

Usage: check_boot_jars.py <package_whitelist_file> <jar1> <jar2> ...
"""
import logging
import os.path
import re
import subprocess
import sys


# The compiled whitelist RE.
whitelist_re = None


def LoadWhitelist(filename):
  """ Load and compile whitelist regular expressions from filename.
  """
  lines = []
  with open(filename, 'r') as f:
    for line in f:
      line = line.strip()
      if not line or line.startswith('#'):
        continue
      lines.append(line)
  combined_re = r'^(%s)$' % '|'.join(lines)
  global whitelist_re
  try:
    whitelist_re = re.compile(combined_re)
  except re.error:
    logging.exception(
        'Cannot compile package whitelist regular expression: %r',
        combined_re)
    whitelist_re = None
    return False
  return True


def CheckJar(jar):
  """Check a jar file.
  """
  # Get the list of files inside the jar file.
  p = subprocess.Popen(args='jar tf %s' % jar,
      stdout=subprocess.PIPE, shell=True)
  stdout, _ = p.communicate()
  if p.returncode != 0:
    return False
  items = stdout.split()
  for f in items:
    if f.endswith('.class'):
      package_name = os.path.dirname(f)
      package_name = package_name.replace('/', '.')
      # Skip class without a package name
      if package_name and not whitelist_re.match(package_name):
        print >> sys.stderr, ('Error: %s contains class file %s, which is not in the whitelist'
                              % (jar, f))
        return False
  return True


def main(argv):
  if len(argv) < 2:
    print __doc__
    return 1

  if not LoadWhitelist(argv[0]):
    return 1

  passed = True
  for jar in argv[1:]:
    if not CheckJar(jar):
      passed = False
  if not passed:
    return 1

  return 0


if __name__ == '__main__':
  sys.exit(main(sys.argv[1:]))

很明顯,如果自己的jar的包名(package name)不在whitelist_re裡面的話,編譯報錯,通過新增log發現whitelist_re來自一個txt檔案: build/core/tasks/check_boot_jars/package_whitelist.txt

build\core\tasks\check_boot_jars\package_whitelist.txt
###################################################
# core-libart.jar
java\.awt\.font
java\.beans
java\.io
java\.lang
java\.lang\.annotation
java\.lang\.ref
java\.lang\.reflect
java\.math
java\.net
java\.nio
............................
...........................
dalvik\..* 
libcore\..* 
android\..* 
com\.android\..*

###################################################
# legacy-test.jar 
junit\.extensions 
junit\.framework android\.test 
android\.test\.suitebuilder\.annotation

.............................

檢視該檔案發現PRODUCT_BOOT_JARS的其他jar的包名都有在這裡定義,仿照檔案格式把自己的包名新增到這裡就OK