早期版本的Android Studio在全局配置NDK的路径信息,但是从Android Studio 3.4版本开始,NDK的路径信息被转移到Project Structure部分去配置了,这变成了一个工程相关的配置,每个工程可以单独配置独立的NDK,SDK版本。
具体操作如下图:
Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由Google公司和开放手机联盟领导及开发。
早期版本的Android Studio在全局配置NDK的路径信息,但是从Android Studio 3.4版本开始,NDK的路径信息被转移到Project Structure部分去配置了,这变成了一个工程相关的配置,每个工程可以单独配置独立的NDK,SDK版本。
具体操作如下图:
在Android Studio 2.2开始的Android Gradle Plugin版本中,Google集成了对cmake的完美支持,而原先的ndkBuild的方式支持也变得更加良好。这篇文章就来说说Android Gradle Plugin与交叉编译之间的一些事,即externalNativeBuild相关的task,主要是解读一下gradle构建系统相关的源码。
子 CMakeLists.txt
1 2 3 4 5 |
option(BUILD_FOR_ANDROID "Build For Android" OFF) if(SYSTEM.Android AND NOT BUILD_FOR_ANDROID) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${NATIVE_LIBRARY_OUTPUT}/${ANDROID_ABI}) endif() |
父 CMakeLists.txt
1 2 |
set(BUILD_FOR_ANDROID ON) add_subdirectory(${CHILD_ROOT_DIR}/ ${CMAKE_CURRENT_SOURCE_DIR}/build) |
执行如下命令的时候:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/Users/xxxx/Library/Android/sdk/cmake/3.6.4111459/bin/cmake --trace-expand \ -H/Users/xxxx/Source/example/demo/android/app \ -B/Users/xxxx/Source/example/demo/android/app/.externalNativeBuild/cmake/debug/arm64-v8a \ -DANDROID_ABI=arm64-v8a \ -DANDROID_PLATFORM=android-21 \ -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=/Users/xxxx/Source/example/demo/android/app/build/intermediates/cmake/debug/obj/arm64-v8a \ -DCMAKE_BUILD_TYPE=Debug \ -DANDROID_NDK=/Users/xxxx/Library/Android/android-ndk-r16b \ -DCMAKE_TOOLCHAIN_FILE=/Users/xxxx/Library/Android/android-ndk-r16b/build/cmake/android.toolchain.cmake \ -DCMAKE_MAKE_PROGRAM=/Users/xxxx/Library/Android/sdk/cmake/3.6.4111459/bin/ninja \ -G"Android Gradle - Ninja" \ -DANDROID_ARM_NEON=TRUE \ -DANDROID_TOOLCHAIN=gcc \ -DANDROID_PLATFORM=android-21 \ -DANDROID_STL=gnustl_shared |
会观察到生成的配置文件中 BUILD_FOR_ANDROID
不一定能生效。
需要如下配置才行:
父 CMakeLists.txt
1 2 |
set(BUILD_FOR_ANDROID ON CACHE BOOL "" FORCE) add_subdirectory(${CHILD_ROOT_DIR}/ ${CMAKE_CURRENT_SOURCE_DIR}/build) |
C and C++ compilers aren’t the fastest pieces of software out there and there’s no lack of programmer jokes based on tedium of waiting for their work to complete.
There are ways to fix the pain though - one of them is ccache. CCache improves compilation times by caching previously built object files in private cache and reusing them when you’re recompiling same objects with same parameters. Obviously it will not help if you’re compiling the code for the first time and it also won’t help if you often change compilation flags. Most C/C++ development however involves recompiling same object files with the same parameters and ccache helps alot.
For illustration, here’s the comparison of first and subsequent compilation times of a largish C++ project:
Original run with empty cache:
1 2 3 4 5 |
$ make -j9 ... real 0m56.684s user 5m31.996s sys 0m41.638s |
Recompilation with warm cache:
1 2 3 4 5 |
$ make -j9 ... real 0m5.929s user 0m11.896s sys 0m8.722s |
CCache is available in repositories on pretty much all distributions. On OS X use homebrew:
1 |
$ brew install ccache |
and on Debian-based distros use apt:
1 |
$ apt-get install ccache |
After ccache is installed, you need to tell CMake to use it as a wrapper for the compiler. Add these lines to your CMakeLists.txt
:
1 2 3 4 5 6 |
# Configure CCache if available find_program(CCACHE_FOUND ccache) if(CCACHE_FOUND) set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) endif(CCACHE_FOUND) |
Rerun cmake
and next make
should use ccache for wrapper.
CCache can even be used on Android NDK - you just need to export NDK_CCACHE
environment variable with path to ccache binary. ndk-build
script will automatically use it. E.g.
1 2 3 |
$ export NDK_CCACHE=/usr/local/bin/ccache $ ndk-build -j9 |
(Note that on Debian/Ubuntu the path will probably be /usr/bin/ccache
)
To see if ccache is really working, you can use ccache -s
command, which will display ccache statistics:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
cache directory /Users/jernej/.ccache primary config /Users/jernej/.ccache/ccache.conf secondary config (readonly) /usr/local/Cellar/ccache/3.2.2/etc/ccache.conf cache hit (direct) 77826 cache hit (preprocessed) 17603 cache miss 46999 called for link 18 compile failed 45 ccache internal error 1 preprocessor error 62 unsupported source language 204 files in cache 48189 cache size 1.2 GB max cache size 20.0 GB |
On second and all subsequent compilations the “cache hit” values should increase and thus show that ccache is working.
Toast
作为 Android
系统中最常用的类之一,由于其方便的api设计和简洁的交互体验,被我们所广泛采用。但是,伴随着我们开发的深入,Toast
的问题也逐渐暴露出来。本文章就将解释 Toast
这些问题产生的具体原因。 本系列文章将分成两篇:
Toast
所带来的问题Toast
问题的解决方案(注:本文源码基于Android 7.0)
当你在程序中调用了 Toast
的 API
,你可能会在后台看到类似这样的 Toast
执行异常:
1 2 3 4 5 6 |
android.view.WindowManager$BadTokenException Unable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running? android.view.ViewRootImpl.setView(ViewRootImpl.java:826) android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:369) android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94) android.widget.Toast$TN.handleShow(Toast.java:459) |
另外,在某些系统上,你没有看到什么异常,却会出现 Toast
无法正常展示的问题。为了解释上面这些问题产生的原因,我们需要先读一遍 Toast
的源码。
以前在 Android Studio 3.2.1上vuh库使用的例子 中实现了一个使用 vuh
库的例子。 那个例子中的 vuh
库是我们编译好 libvuh.so
之后直接引用的,我们下面实现通过直接编译代码实现整合。
尝试过使用 ExternalProject_add
跟 include
的方式包含 vuh
库,但是都不是很成功。
其中 ExternalProject_add
导入的项目只能编译一次,即使指定 BUILD_ALWAYS 1
也没用,这个应该是 Ninja
导致的问题,导致当出现多个 ABI
或者 vuh
库代码变动之后,不能重新编译,出现各种编译错误。
使用 include
包含的项目会导致路径信息不正确,无法找到源代码文件。
最后使用 add_subdirectory
实现。
修改之后的几个关键文件如下:
注意: VUH_ROOT_DIR
这个变量中指定 vuh
库代码的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.8) # for Vulkan SET(Vulkan_INCLUDE_DIR ${ANDROID_NDK}/sources/third_party/vulkan/src/include/) SET(Vulkan_LIBRARIES ${ANDROID_NDK}/platforms/${ANDROID_PLATFORM}/arch-${ANDROID_ARCH_NAME}/usr/lib) if(X86_64) SET(Vulkan_LIBRARIES ${Vulkan_LIBRARIES}64) endif() SET(Vulkan_LIBRARIES ${Vulkan_LIBRARIES}/libvulkan.so) add_library(vulkan SHARED IMPORTED) set_target_properties(vulkan PROPERTIES IMPORTED_LOCATION ${Vulkan_LIBRARIES}) # for vuh add_definitions(-DVK_USE_PLATFORM_ANDROID_KHR=1 -DVULKAN_HPP_TYPESAFE_CONVERSION=1) SET(VUH_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../vuh/) SET(VUH_BUILD_TESTS OFF) SET(VUH_BUILD_DOCS OFF) SET(VUH_BUILD_EXAMPLES OFF) add_subdirectory(${VUH_ROOT_DIR}src/ ${CMAKE_CURRENT_SOURCE_DIR}/build/) # for example SET(Vuh_INCLUDE_PATH ${VUH_ROOT_DIR}src/include) include_directories(${Vulkan_INCLUDE_DIR}) include_directories(${Vuh_INCLUDE_PATH}) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp) add_dependencies(native-lib vuh) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log android) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. native-lib vulkan vuh ${log-lib}) |
注意:由于 vuh
库需要 CMake 3.8
。因此,我们需要手工指定CMake
版本为3.10.2
。
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
apply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { applicationId "com.mobibrw.vuhandroid" minSdkVersion 24 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { version "3.10.2" cppFlags "-std=c++14 -v -g" } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } externalNativeBuild { cmake { version "3.10.2" path "CMakeLists.txt" } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' } |
如果出现如下错误:
1 2 |
CMake Error: CMake was unable to find a build program corresponding to "Ninja". CMAKE_MAKE_PROGRAM is not set. You probably need to select a different build tool. -- Configuring incomplete, errors occurred! |
则执行如下操作:
1 |
$ brew install ninja |
如果出现如下错误:
1 2 3 |
* What went wrong: Execution failed for task ':app:transformNativeLibsWithMergeJniLibsForDebug'. > More than one file was found with OS independent path 'lib/armeabi-v7a/libvuh.so' |
则删除代码中的 jniLibs/armeabi-v7a/libvuh.so
即可解决问题。
完整的例子点击此处下载 vuhAndroid
目前最新版本的 Android Studio 3.3.1
默认使用CMake 3.6
版本,但是已经支持 CMake 3.10.2
版本了。
新版本的 CMake 3.10.2
新增了 FindVulkan.cmake
等一系列的改进,对于很多项目来说,会更友好。
目前默认依旧使用 CMake 3.6
版本,但是可以手工指定使用 CMake 3.10.2
。
继续阅读macOS Mojave (10.14.3) Android Studio 3.3.1 指定使用CMake 3.10.2版本
开发的时候遇到了这个问题,记录下下次就知道了。这个是因为在 sdk22
以后的版本不在支持了!!!
程序包 org.apache.http.conn.util
不存在,然后搜索了下,在 app
的 build.gradle
文件中加上useLibrary 'org.apache.http.legacy'
就可以了。
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
android { compileSdkVersion 23 buildToolsVersion '23.0.3' useLibrary 'org.apache.http.legacy' defaultConfig { applicationId "com.ah.woyi" minSdkVersion 14 targetSdkVersion 21 versionCode 1 versionName "1.0" } |
Android Studio 3.2.1
上 vuh
库使用的例子,首先使用 基于Vulkan的GPGPU计算框架Vuh 编译出 Android
版本的动态库,然后依照如下步骤建立工程。
如果使用cmake
构建项目,配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# macOS `brew install ccache` ubuntu `apt-get install ccache` $ export ANDROID_NDK_HOME=~/Library/Android/sdk/ndk-bundle $ cmake .. \ -DNDK_CCACHE=ccache \ -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \ -DANDROID_ABI="armeabi-v7a" \ -DCMAKE_BUILD_TYPE=Release \ -DANDROID_STL=c++_static \ -DANDROID_NATIVE_API_LEVEL=android-24 \ -DANDROID_TOOLCHAIN=clang $ make -j4 |
也就是定义NDK_CCACHE=ccache
。
如果使用ndk-build
构建项目,配置如下:
1 2 3 4 5 6 7 |
# macOS `brew install ccache` ubuntu `apt-get install ccache` $ export NDK_CCACHE=ccache $ export USE_CCACHE=1 $ ndk-build |
也就是增加两个环境变量。
不指定缓存目录的情况下,缓存文件的目录一般在当前用户名下的.ccache
目录下,时间长了,可能会产生很多的缓存文件,需要定时清理,当然也可以限制缓存的最大大小,让ccache
根据需要进行淘汰。