The title says it all (surprisingly).

This is my test.

Test source code - a.c

#include <stdio.h>


int main() {
    int a = 0;
    printf("%d\n", 1 / a);
    printf("Done\n");
    return 0;
}

Build it for x64 (ubuntu 22.04).

$ lsb_release -a
No LSB modules are available.
Distributor ID:    Ubuntu
Description:    Ubuntu 22.04.3 LTS
Release:    22.04
Codename:    jammy

$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0

$ gcc a.c
$ ./a.out
Floating point exception (core dumped)

However, built it for aarch64 with android-ndk toolchain.

$ android/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang a.c
$ adb push a.out /data/a.out
$ adb shell /data/a.out
0
Done

No error! Just gives ZERO!!

'Ninja' has implicit dependency on input '.ninja' file.

Let's thing about following example.


< build.ninja >


rule hello

     command = echo ${msg}


build aaaa: hello

      msg = aaaa


build build.ninja: hello

      msg = build.ninja


-----


$ ninja yyyyyyy

[1/1] echo build.ninja

build.ninja

ninja: error: unknown target 'yyyyyyy'



---------------



Ninja try to build input '.ninja' file if rule for the '.ninja' is defined.

And then Ninja try to build given target.


Here is the way to compile x86 goldfish and run on emulator


* AOSP Marshmallow + Goldfish 3.4

* ARCH=x86 (You don't need to set SUBARCH)

* Compiler version : gcc-4.8 (prebuilt x86 compiler from Android NDK)

  (Set CROSS_COMPILE environment variable)


$ make ARCH=x86 CC="${CROSS_COMPILE}gcc -mno-android" goldfish_defconfig

$ make ARCH=x86 CC="${CROSS_COMPILE}gcc -mno-android" -j40


And use 'arch/x86/boot/bzImage' as 'kernel-qemu' image


Please note that you SHOULD use '-mno-android' option to build linux kernel with NDK toolchains.
The reason is, '-mandroid' option is enabled by default at GCC in NDK(file name is something like *-*-android*)
Remember that NDK toolchain is used to build native executable or libraries run on Android devices.
And, in general, build environment of linux kernel is 'GCC' + 'GLIBC'.
(Note that, linux kernel binary even if it runs on Android device, it doesn't have any dependency on Android platform(ex. bionic or libc))
So, to build linux kernel, '-mandroid' option should be disabled by using '-mno-android' option.


/libcore/luni/src/main/java/java/util/jar/JarVerifier.java

여기 source를 확인해 보면,

+ '.RSA', '.DSA', '.EC' 로 끝나는 file을 찾고, 같은 이름의 '.SF' 파일을 찾는다.(Android의 경우는, 'CERT.RSA' 와 'CERT.SF' 파일)

    : '.RSA', '.DSA', '.EC' : Certification File.

    : '.SF' : Signature File

    : Signature file을 certification file을 통해서 먼저 verification한다.

+ .SF 파일의 각 file entry에 대해서 '-Digest' 값을 읽어 들인다.


<< PackageParser >>

'Signing Verifier'에서 읽어들인 "file entry:<HASH>" 값 mapping table을 이용해서, zip file안에 있는 file들에 대해 hash를 검사한다.

    : System APK에 대해서는 Manifest의 hash만 검사 (trusted app.) 그렇지 않은 경우 모든 file - 'META-INF/' directory제외 - 에 대해서 검사.

    



이미 많이 알려진 topic이지만.. 정리차원에서...


* 표준적인 방법은 'startForeground()' API를 통하는 방법이다.

그렇지만, 'startForeground()' 를 이용할 경우에는 반드시 'Notification()'을 띄워야 한다.


* 만약 Notification없이 죽지 않는 Service를 만들때, 그 service를 platform key로 signing할 수 있다면(즉 platform app인 경우)

<android:persistent='true' />를 이용할 수 있다.

이는 '<application', '<service', '<activity' 에 각각 적용 가능하다.

다만, Persistent App의 경우 항상 memory에 상주한다. (Kill 로 죽이더라도 곧바로 재시작한다.)


/libcore/luni/src/main/java/java/util/jar/JarVerifier.java


Here is steps for verifying package with it's signature at META-INF in Android system.

- Find files ends with '.RSA', '.DSA' or '.EC'(Certification file) and then find '.SF' file(Signature File) that has same basename.

  (In case of Android, 'CERT.RSA' and 'CERT.SF' file)

- Verifying Signature File by using Certification file => Signature File is verified.

- Read '-Digest' values from CERT.SF file. => Valid hash value for files are read.

- Then, when parsing package(Apk), all file entries except for files in 'META-INF' directory, are scanned and compared with corresponding hash value in CERT.SF.


Interesting point is, in Android, verification is processed based on file entries in APK.

That is, removing some entries from APK doesn't make any problem in terms of signature verification. :)

 Abi관련 변수들


Build.CPU_ABI

Build.SUPPORTED_ABIS
ro.product.cpu.abilist64
ro.product.cpu.abilist32


=========================

64bit, 32bit library의 경우


BaseDexClassLoader 에서 참조하는 native library는 null인데, 이는 default library path를 사용하는 것이고...

코드를 쫓아가다보면...

'java.library.path"

에서 library directory를 가져오는데(/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java) 이 값은 default로 LD_LIBRARY_PATH 환경변수값을 사용한다.(java_lang_System.cpp)

android_get_LD_LIBRARY_PATH() 함수를 찾아서 가보면, kDefaultLdPaths 에서 값을 읽어오는데, 아래와 같이 정의되어 있다.


91static const char* const kDefaultLdPaths[] = {
92#if defined(__LP64__)
93  "/vendor/lib64",
94  "/system/lib64",
95#else
96  "/vendor/lib",
97  "/system/lib",
98#endif
99  NULL
100};
101

그리고


system library의 경우

system/lib/

system/lib64/

vendor/lib/

vendor/lib64


========================

system library의 경우는 위와 같고... APK에 포함된 library의 경우는... aapt를 확인해야 하는데, 

/frameworks/base/tools/aapt/Command.cpp

를 보면, 

1981            AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib");
1982            if (dir != NULL) {
1983                if (dir->getFileCount() > 0) {
1984                    SortedVector<String8> architectures;
1985                    for (size_t i=0; i<dir->getFileCount(); i++) {
1986                        architectures.add(ResTable::normalizeForOutput(
1987                                dir->getFileName(i).string()));
1988                    }


코드가 보이는데... 즉 "lib/<archtecture-name>"의 형태로 directory가 구성되었다는 것을 알 수 있다.


====================================

APP이 뜰때, 64bit인지 32bit인지 확인해서 zygote64를 base로 뜰건지 zygote32를 base로 뜰건지 알아야 하는데,

Process.java

에서

openZygoteSocketIfNeeded(String abi)

처럼 abi가 넘어가게 된다.

이 abi값은 proces start에 argument로 넘어오는데,

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

3192            Process.ProcessStartResult startResult = Process.start(entryPoint,
3193                    app.processName, uid, uid, gids, debugFlags, mountExternal,
3194                    app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
3195                    app.info.dataDir, entryPointArgs);

3177            String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
3178            if (requiredAbi == null) {
3179                requiredAbi = Build.SUPPORTED_ABIS[0];
3180            }
3181
3182            String instructionSet = null;
3183            if (app.info.primaryCpuAbi != null) {
3184                instructionSet = VMRuntime.getInstructionSet(app.info.primaryCpuAbi);
3185            }


에서 requiredAbi argument가 있고... default abi를 사용하긴 하나, 결국 package가 지원하는 abi를 알아야 한다.



/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

5726                    final int copyRet;
5727                    if (isAsec) {
5728                        copyRet = NativeLibraryHelper.findSupportedAbi(handle, abiList);
5729                    } else {
5730                        copyRet = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
5731                                nativeLibraryRoot, abiList, useIsaSpecificSubdirs);
5732                    }
5733
5734                    if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
5735                        throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
5736                                "Error unpackaging native libs for app, errorCode=" + copyRet);
5737                    }
5738
5739                    if (copyRet >= 0) {
5740                        pkg.applicationInfo.primaryCpuAbi = abiList[copyRet];
5741                    } else if (copyRet == PackageManager.NO_NATIVE_LIBRARIES && cpuAbiOverride != null) {
5742                        pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
5743                    } else if (needsRenderScriptOverride) {
5744                        pkg.applicationInfo.primaryCpuAbi = abiList[0];
5745                    }


findSupportedAbi() 

com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass clazz,

=> 413static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {


결국 "/lib/<abi-name>"이란 규칙을 이용해서 apk가 지원하는 ABI를 찾는다.

그런데, PackageManagerService를 보면, package가 '.apk' file인 경우 - Monolithic apk - 와 directory이 경우 - Cluster apk - 두 가지로 나뉘어 지는 것을 알 수 있다.

두 경우 모두 cpu Abi를 결정하기 위해서 위의 방법을 사용하는데, 두 경우 모두


<package file/dir>/lib/<cpu abi>


형태의 구성을 가정하고 있다.

따라서, apk build 자체가 위의 구성으로 나오는 package build와 실제 so는 system에 두고 link만들 거는 system apk 모두를 cover하고 있다.


ex.

Monolithic


Gallery.apk

|-AndroidManifest.xml

|-...

|-lib/armeabi-v7a/xxx.so

|...




Cluster (PDK build시 "/system/app/<package name>" directory)


Gallery/ ('/system/app/Gallery')

|-Gallery.apk

|-x86_64/Gallery.odex

|-lib/x86_64/xxx.so



두 경우 모두 "<Gallery apk/dir>/lib/<cpu abi>"형태로 구성되고 이를 보고 판단한다.


아래 코드는 추가적으로 참고하자..

5686                    if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
5687                        if (isAsec) {
5688                            abi64 = NativeLibraryHelper.findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
5689                        } else {
5690                            abi64 = NativeLibraryHelper.copyNativeBinariesForSupportedAbi(handle,
5691                                    nativeLibraryRoot, Build.SUPPORTED_64_BIT_ABIS,
5692                                    useIsaSpecificSubdirs);
5693                        }
5694                    }
5695
5696                    maybeThrowExceptionForMultiArchCopy(
5697                            "Error unpackaging 64 bit native libs for multiarch app.", abi64);
5698
5699                    if (abi64 >= 0) {
5700                       


뭐 결국은, apk의 "/lib/<abi-name>"이 platform이 지원하는 64bit abi에 포함되면, 64bit apk로 판단하는 방식이다...

즉, APK가 어떤 abi를 지원하는지는 manifest file 등등 이런 곳에 따로 기록되어 있는것이 아니고, native library의 "/lib/<abi-name>"규칙에 의존해서 찾는다.


Platform build시 jni library들은 default로 모두 'system/lib'쪽에 들어가게 되어 있는데 - APK에 lib이 포함되지 않는다. - 그럼, 해당 APK의 ABI어떻게 알 수 있는가?

(최초 booting시 해당 APK를 AOT(Ahead Of Time) compile을 위해서는 어떤 target ABI로 compile할지가 정해져야 한다.)


일단 생각해보면, 어차피 VM(DEX)에만 의존하는 APK라면 특별히 ABI에 영향을 받지 않으므로 그냥 platform default ABI로 인식하고, AOT compile을 수행하면 된다. 그렇지만, 사용하는 native library들이 있고, 이 library를 load해야하는 APK라면, ABI에 따라서 어떤 zygote(32bit 혹은 64bit)에서 fork될 것인가를 결정해야 하므로, ABI를 system이 알아야할 필요가 있다.


이 경우는, 'LOCAL_MULTILIB := 32' 같은 방식으로 second abi를 사용하도록 정의하고 빌드하면, 'system/app/<package>'쪽에 보면


*** x86_64 용 TARGET ABI로 빌드하고, 'LOCAL_MULTILIB := 32'를 사용하는 Gallery2를 예로 들면...

Gallery2

- Gallery2.apk

- lib/x86/<xxx>.so -> /system/lib/<xxx>.so (symbolic link)

- x86/Gallery2.odex



즉, 제대로 32bit 용으로 빌드되어 있다. (그렇지만, Gallery2.apk내부에는 'lib/<abi>' 형태의 directory가 없다.

그렇지만, 만약, 32bit용으로 DEX가 pre-optimization되어 있지 않다면, 어떻게 Gallery2가 32bit용 package인지 system이 알고 32bit용으로 dex를 optimization - ART의 경우 AOT compile - 시킬수 있을까?



/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

6485        } else {
6486            // Cluster install
6487            info.nativeLibraryRootDir = new File(codeFile, LIB_DIR_NAME).getAbsolutePath();
6488            info.nativeLibraryRootRequiresIsa = true;
6489
6490            info.nativeLibraryDir = new File(info.nativeLibraryRootDir,
6491                    getPrimaryInstructionSet(info)).getAbsolutePath();
6492
6493            if (info.secondaryCpuAbi != null) {
6494                info.secondaryNativeLibraryDir = new File(info.nativeLibraryRootDir,
6495                        VMRuntime.getInstructionSet(info.secondaryCpuAbi)).getAbsolutePath();
6496            }
6497        }
6498    }




scanPackageDirtyLI() -> 여기서 installer를 이용해서 app data directory / lib directory 등을 만든다. nativeFindSupportedAbi => copyNativeBinariesForSupportedAbi : 여기서 abi를 가지고 instrunction set directory로 copy한다. 이때 nativeLibraryRootDir 로 복사하는데, 이 nativeLibraryRootDir 로 될 수 있는게 <bundled app> 은 final boolean bundledApp = isSystemApp(info) && !isUpdatedSystemApp(info); [[ Monolithic ]] Monolithic && bundled "/system/lib[64]/<apkname>" Monolithic 이고 Non-bundled "/data/app-lib/" 이다 [[ Cluster ]] info.nativeLibraryDir = new File(info.nativeLibraryRootDir, getPrimaryInstructionSet(info)).getAbsolutePath(); if (info.secondaryCpuAbi != null) { info.secondaryNativeLibraryDir = new File(info.nativeLibraryRootDir, VMRuntime.getInstructionSet(info.secondaryCpuAbi)).getAbsolutePath(); } 즉 Cluster 의 경우 NativeLibraryDir은 abi가 아니라 instruction set 이름으로 찾아들어간다. VMRuntime.java 38 static { 39 ABI_TO_INSTRUCTION_SET_MAP.put("armeabi", "arm"); 40 ABI_TO_INSTRUCTION_SET_MAP.put("armeabi-v7a", "arm"); 41 ABI_TO_INSTRUCTION_SET_MAP.put("mips", "mips"); 42 ABI_TO_INSTRUCTION_SET_MAP.put("mips64", "mips64"); 43 ABI_TO_INSTRUCTION_SET_MAP.put("x86", "x86"); 44 ABI_TO_INSTRUCTION_SET_MAP.put("x86_64", "x86_64"); 45 ABI_TO_INSTRUCTION_SET_MAP.put("arm64-v8a", "arm64"); 46 }


VMRuntime.getInstructionSet(info.secondaryCpuAbi)) 에서 arch이름을 찾는 규칙이 위와 같다.

즉 cpuabi 는 instruction set 이름을 찾기 위해서 사용되지 그 자체는 package manager service에서 사용되지 않는다.

재미있는 것은 build system에서는

"cpu abi" -> "arch"

오 가타은 이름을 사용하고 있고, package manager service에서는

"cpu abi" -> "instruction set"

을 사용한다.

즉, 같은 이름을 build system과 package manager service에서, 'arch', 'instruction set'이라는 이름으로 서로 다르게 사용하고 있다.




실제 emulator를 실행시킨 후 위의 - 첫번째 booting에서 apk가 install된다. - package의 data directory를 가 보면.


$ ls -l /data/data/<package-name>

lib -> /data/app-lib/<package-name>


ex.

root@generic_x86_64:/data/data/com.android.gallery # ls -l lrwxrwxrwx install install 2014-11-10 14:13 lib -> /data/app-lib/com.android.gallery


그리고 아래의 code를 보자.


/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

6477            } else {
6478                final String apkName = deriveCodePathName(codePath);
6479                info.nativeLibraryRootDir = new File(mAppLib32InstallDir, apkName)
6480                        .getAbsolutePath();
6481            }


위의 코드에서 'mAppLib32InstallDir'이 바로 '/data/app-lib' directory이다.

이렇게 자신의 library를 찾아가게 된다.







Rough Block Diagram

[Display Device] <>--- [Logical Display] <>--- [WindowManager]

DisplayInfo

has followings (Initial values are set as information from display device).

:logicalHeight

:logicalWidth

:appWidth

:appHeight


DisplayDevice

has followings

:mCurrentLayerStackRect

:mCurrentDisplayRect


shown on the display.

|

v

[LayerStackRect] ---(project to)---> [DisplayRect]

^

|

This value is outbound of WindowManager.


Above information is very rough and... NOT strictly verified.


Here is useful tool supported by Google to handle displaying area and density - wm.

You can find it at '/system/bin/wm'


root@generic:/system/bin # ./wm reset usage: wm [subcommand] [options] wm size [reset|WxH] wm density [reset|DENSITY] wm overscan [reset|LEFT,TOP,RIGHT,BOTTOM]

Very useful isn't it!



Android VM은 일반적인 Java VM과 마찬가지로 boot class path와 class path를 지원한다.

그렇지만, Android App(APK)에서 위의 내용들이 어떻게 사용되고 있는가에 대한 점은 상세히 분석해 볼 필요가 있다.

왜냐하면, Android에서는 App(APK)가 처음부터 loading되는 것이 아니고 zygote를 통해서 fork되는 방식이기 때문에  Java의 경우와는 약간 다르기 때문이다.


Class Loader 의 종류와 내용.


VMClassLoader / BootClassLoader

BOOTCLASSPATH만을 load하는 기본적인 class loader


System Class Loader

BootClassLoader + "CLASSPATH" 를 가지는 Loader.

VM이 최초 "static main"을 invoke시킬때 사용하는 class loader이다.

근거:

startVm() -> DvmStartup() -> dvmPrepMainThread()

: SystemClassLoader를 Thread의 ContextClassLoader로 setting함.

AndroidRuntime::start()pEnv->FindClass() - Function pointer to 'dalvik/vm/Jni.cpp:FindClass' - 를 이용해서 "static main"을 invoke하는데, 이때 SystemClassLoader가 사용됨.


Context Class Loader

SystemClassLoader + "Package private configuration"을 가지는 loader.

VM이 뜬 이후, Android context instance - ex. Activity - 를 시작할때 - ex. performLaunchActivity() - 사용되는 class loader로 기본 System Class Loader에 package private configuration(LoadedApk.java) - ex. shared java library - 을 포함한다.

근거:

LoadedApk.getClassLoader() 함수를 자세히 살펴볼 필요가 있는데, package정보의 java shared library나 native shared library정보 등을 이용해서 PathClassLoader를 생성하고 이를 이용하는데, 이때 base class loader가 default로, SystemClassLoader이기 때문이다.


여기서 또 한가지 중요한 내용은, Java의 경우, class가 최초 reference될때, referer가 load될때 사용된 classLoader를 이용해서, reference되는 class도 load된다는 것이다. 이것은 Android VM의 경우도 마찬가지인데 예로, dalvik의 OP_NEW_INSTANCE op code처리 부분을 보면, dvmResolveClass() 를 call하고, 함수 내부의 구현을 보면, referer의 class loader 를 이용하는 것을 볼 수 있다.


이제 정리할 시간이다.

Android의 경우, 최초 vm이 뜰때는 System Class Loader가 사용되나, context가 수행될 때는 Context Class Loader가 사용된다. 따라서, Package(APK)에 추가적으로 사용할 library를 기록해 두면, Context내부에서는 정상적으로 사용이 가능하다.


그러면, Package 에서 사용할 library들은 어떤식으로 정의할 수 있을까?


먼저 사용할 library가 permission file에 정의되어 있고, platform build시 /system/etc/permissions 에 위치해야 한다.

(당연히, 해당 library도 정해진 위치에 있어야 한다.)


com.my.android.test.xml

<permissions>

<library name="com.my.android.test"

         file="/system/framework/com.my.android.test.jar" />

</permissions>


이제 위의 library는 package manager에 의해 shared library로 인식된다.


Package에서 사용하기 위해서는, package의 AndroidManifest.xml에 application element의 sub element로 아래 'uses-library' element를 추가해야 한다.


AndroidManifest.xml

<uses-library android:name="com.my.android.test" />


이렇게 정의되면, package manager service가 Context Class Loader에 해당 library를 추가해 준다.


또 한가지 짚고 넘어가야 할 것은, CLASSPATH 에 대한 부분이다.

CLASSPATH의 경우, VM이 시작할때, setting되는 java.class.path property를 읽는데, 이 값은 Dvm의 classPathStr 값이다.


[[ TO DO ]]

그런데, 재미있는 사실은, system의 CLASSPATH 환경변수를 설정하더라도, Zygote에서 fork되는 App에서는 이를 바로 사용하지 못하는데 (실험적. 확인 필요)이 부분은 좀더 분석해 봐야 한다.



====== Rough Reference - Starting new Activity ======


ActivityStackSupervisor
startActivityLocked
startActivityUncheckedLocked

ActivityStack (target stack)
startActivityLocked

ActivityManagerService

attachApplicationLocked

ActivityStackSupervisor

resumeTopActivityLocked

ActivityManagerService.java
startProcessLocked() -> Process.start("android.app.ActivityThread", app.processName, uid, uid, gids ....)

Process.java
Process.start()
startViaZygote()
zygoteSendArgsAndGetResult() === send argument list via socket ===> zygote

<<< Socket connection >>>

ZygoteInit
registerZygoteSocket()
acceptCommandPeer()

ZygoteConnection
readArgumentList()

Zygote.java
forkAndSpecialize

--- child process ---

'WrapperInit.execApplication' or 'execStandalone'
# executing execv -> "/system/bin/app_process /system/bin --application --nice-name=xxx com.android.internal.os.WrapperInit <pipe fd> <targetSdkVersion> <shell args>
<shell args> : Args from Process.zygoteSendArgsAndGetResult <= which comes from startProcessLocked ("android.app.ActivityThread" ...)
# executing execv -> "/system/bin/dalvikvm -classpath '...' ...

--- execv ---

AppRuntime : AndroidRuntime -> "com.android.internal.os.RuntimeInit"

AndroidRuntime.start()
: startVm()
...
# starts Java startClass or starts "main" method.

RuntimeInit.main()

AndroidRuntime.cpp:: com_android_internal_os_RuntimeInit_nativeFinishInit()

app_main::AppRuntime.onStarted

AndroidRuntime.callMain()

*** <class>.main() *** => WrapperInit.main (see app_process argument above....).

RuntimeInit
wrapperInit()
applicationInit()
invokeStaticMain()

static ActivityThread.main : <= See WrapperInit.execApplication.

ActivityThread
attach()
ActivityManager.attachApplication() ==> ActivityManagerService.attachApplication()
# enter message loop!

ActivityManagerService
attachApplication()
attachApplicationLocked()
:--- attaching applications...

ActivityStackSupervisor.attachApplication()

ActivityStackSupervisor
realStartActivityLocked()

ActivityThread.scheduleLaunchActivity()
-> queue message : ActivityThread.LAUNCH_ACTIVITY

'Domain > Android' 카테고리의 다른 글

Multiple ABI and 64bit on Lolipop  (2) 2014.11.08
[Android] WindowManager & Display Area  (0) 2014.08.08
Create and use java library from packages.  (0) 2014.02.03
[NDK] 불편한 진실... 00  (0) 2013.11.25
Android resource compiler의 한계...  (0) 2013.11.09

system에 library를 embedding하고, 그것을 apk가 사용하고자 할때... (여러 apk에서 공유가능.)


Sample lib permission - com.my.lib.xml

<permissions>
    <library
        name="com.my.lib"
        file="/system/framework/com.my.lib.jar" />
</permissions>

library definition을 정의한, 위의 xml을 이용하면면, 해당 library를 system에 install하고, uses-library tag - AndroidManifest.xml - 를 이용해서 loading할 수 있게 된다.

Example uses-library

<uses-library
    android:name="com.my.lib"
    android:required="true" />

Sample Android.mk

LOCAL_PATH:= $(call my-dir)

# lib def
# -------
include $(CLEAR_VARS)
LOCAL_MODULE := com.my.lib.xml
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)

# the library
# -----------
include $(CLEAR_VARS)
LOCAL_MODULE := com.my.lib
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_SRC_FILES := $(call all-subdir-java-files)
include $(BUILD_JAVA_LIBRARY)

# the package
# -----------
include $(CLEAR_VARS)
LOCAL_MODULE := myApk
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := platform
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_REQUIRED_MODULES := com.my.lib.xml com.my.lib
include $(BUILD_PREBUILT)

Explanation

* Product가 myApk 를 설치한다면, 위의 두 module 역시 설치하도록 하기 위해서 LOCAL_REQUIRED_MODULES 를 사용한다.

* Android framework은 library, feature 등을 등록하기위해서, $(TARGET_OUT_ETC)/permissions/*.xml 을 scan한다.


이렇게 하면, myApk가 load될때, uses-library 에 기록된, com.my.lib 이 같이 load된다.
단, system class loader는 여전히 BOOTCLASSPATH를 기준으로 하고 있고, Application context가 제공하는 class loader를 사용해야 위의 library가 load된 class loader를 사용할 수 있다.

다시 말하면, library에 정의된 class를 loading하기 위한 방법/예 는 아래와 같다.(class loader를 이용한 것은 example을 보여주기 위한 것 뿐, 그냥 원하는 class를 import해서 사용해도 된다. - 이미 load된 class 이므로...)

ClassLoader cloader; // cloader = new PathClassLoader("/system/framework/com.my.lib.jar", ClassLoader.getSystemClassLoader()); => (*A) // cloader = ClassLoader.getSystemClassLoader(); => (*B) // cloader = getApplicationContext().getClassLoader(); => (*C) cloader.loadClass("com.my.lib.SampleClass");


*A, *C : 정상적으로 SampleClass가 loading 됨.

*B : SampleClass를 load할 수 없음.

Done.



+ Recent posts