结尾
最后小编想说:不论以后选择什么方向发展,目前重要的是把Android方面的技术学好,毕竟其实对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
高级UI,自定义View
UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。
不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
上面出现了几个命令符号,不了解了可以看一下如下解释:
–sysroot=?: 使用 ?作为这一次编译的头文件与库文件的查找目录,查找下面的 usr/include 目录。
-isystem ?(主要中间有一个英文空格) : 使用头文件查找目录,覆盖 --sysroot, 查找 ?/usr/include 目录下面的头文件。
-isystem ?(主要中间有一个英文空格): ** 指定头文件的查找路径。
-I?: 头文件的查找目录,I 是大写。
这样编译之后还是会报一个 asm/types.h 文件找不到,我们还要继续修改一下路径,如下
/root/android/ndk/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc --sysroot=/root/android/ndk/android-ndk-r17c/platforms/android-21/arch-arm -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include -isystem /root/android/ndk/android-ndk-r17c/sysroot/usr/include/arm-linux-androideabi -pie -o test test.c
这样就能编译成一个 Android 平台可执行的文件了,这样看起来路径太多不易阅读,大家可以参考我提供的全局变量配置来进行设置,最后一行命令解决,如下:
$NDK_GCC_arm $NDK_CFIG_arm -pie -o test test.c 复制代码
可以看到,我们使用 Android NDK 编译出来的可执行文件已经在 Linux 平台下不可执行了。下面我们将 test 文件导入到 手机 /data/local/tmp 目录。
- 将 NDK 交叉编译出来的 test 可执行文件,导入 Android 手机中并执行 test 文件。
根据上面的录屏,我们知道已经成功的在 Android 设备下执行了 NDK 交叉编译后的 test 文件了。
下面我们利用 NDK 工具交叉编译 test.c 输出静态动态库。
动态库 & 静态库
编译静态库
- 将 test.c 使用 NDK GCC 编译为 .o 文件 ,命令如下:
$NDK_GCC_arm $NDK_CFIG_arm -fpic -c test.c -o test.o
如果出现如下文件,证明已经成功了。
- 使用 NDK arm-linux-androideabi-ar 工具将 test.o 文件生成 test.a 静态库,命令如下:
$NDK_AR_arm r test.a test.o
之后我们把 test.a 文件导入到 AS 中,来对 .a 的使用。
编译动态库
在编译动态库的时候我们需要指定 -fPIC -shared 额外参数给编译器,完整命令如下:
$NDK_GCC_arm $NDK_CFIG_arm -fpic -shared test.c -o libTest.so
动态库与静态库的区别
在平时工作中我们经常把一些常用的函数或者功能封装为一个个库供给别人使用,java开发我们可以封装为 ja r包提供给别人用,安卓平台后来可以打包成 aar 包,同样的,C/C++ 中我们封装的功能或者函数可以通过静态库或者动态库的方式提供给别人使用。
Linux 平台静态库以 .a 结尾,而动态库以 .so 结尾。
那静态库与动态库有什么区别呢?
1. 静态库
与静态库连接时,静态库中所有被使用的函数的机器码在编译的时候都被拷贝到最终的可执行文件中,并且会被添加到和它连接的每个程序中:
优点:运行起来会快一些,不用查找其余文件的函数库了。
缺点:导致最终生成的可执行代码量相对变多,运行时, 都会被加载到内存中. 又多消耗了内存空间。
2. 动态库
与动态库连接的可执行文件只包含需要的函数的引用表,而不是所有的函数代码,只有在程序执行时, 那些需要的函数代码才被拷贝到内存中。
优点:生成可执行文件比较小, 节省磁盘空间,一份动态库驻留在内存中被多个程序使用,也同时节约了内存。
缺点:由于运行时要去链接库会花费一定的时间,执行速度相对会慢一些。
静态库是时间换空间,动态库是空间换时间,二者均有好坏。
如果我们要修改函数库,使用动态库的程序只需要将动态库重新编译就可以了,而使用静态库的程序则需要将静态库重新编译好后,将程序再重新编译一遍。
mk & cmake
上一小节我们通过 NDK 交叉编译了 test.c 为动态静态库,那么该小节我们就基于 makefile 和 cmake 来构建一个 C/C++ 的 Android 程序, 并使用 test .a /libTest.so
mk
Android.mk 是在 Android 平台上构建一个 C 或者 C ++ 语言编写的程序系统的 Makefile 文件,不同的是, Android 提供了一些列内置变量来提供更加方便的构建语法规则。Application.mk 文件实际上是对应用程序本身进行描述的文件,它描述了应用程序要针对哪些 CPU 架构打包动态 so 包、要构建的是 release 包还是 debug 包以及一些编译和链接参数等。
语法基础
1. Android.mk
- LOCAL_PATH :=$(call my-dir)
返回当前文件在系统中路径,Android.mk 文件开始时必须定义该变量。
-
include $(CLEAR_VARS), 表明清楚上一次构建过程的所有全局变量,因为在一个 Makefile 编译脚本中,会使用大量的全局变量,使用这行脚本表明需要清除掉所有的全局变量。
-
LOCAL_SRC_FILES, 要编译的 C 或者 CPP 的文件,注意这里不需要列举头文件,构建系统会自动帮组开发者依赖这些文件。
-
LOCAL_LDLIBS:= -L定编译过程所依赖的提供的动态静态库,变量代表的是下面的目录(SYSROOT)/usr/lib -Ilog -IOpenSLES -IGLESv2 -IEGL -Iz,定编译过程所依赖的 NDK 提供的动态静态库, SYSROOT 变量代表的是 NDK_ROOT 下面的目录 NDK 提供的动态与静态库,SYSROOT 变量代表的是 NDK_ROOT 下面目录 $NDK_ROOT/platforms/android-21/arch-arm, 而在这个目录的 usr/lib/ 目录下有很多对应的 so 的动态库以及 .a 的静态库。
-
LOCAL_CFLAGS , 编译 C 或者 CPP 的编译标志,在实际编译的时候会发送给编译器。比如常用的实例是加上 -DAUTO_TEST , 然后在代码中就可以利用条件判断 #ifdef AUTO_TEST 来做一些与自动化测试相关的事情。
-
LOCAL_LDFLAGS, 链接标志的可选列表,当对目标文件进行链接以生成输出文件的时候,将这些标志带给链接器。该指令与 LOCAL_LDLIBS 有些类似,一般情况下,该选项会用于指定第三方编译的静态库,LOCAL_LDLIBS 经常用于指定系统的库(比如 log、OpenGLES、EGL 等)。
-
LOCAL_MODULE, 该模块的编译的目标名,用于区分各个模块,名字必须是唯一并不包含空格的,如果编译目标是 so 库,那么该 so 库的名称就是 lib 项目名 .so。
-
include $(BUILD_SHARED_LIBRARY) ,其实类似的 include 还有很多,都是构建系统提供的内置变量,该变量的意义是构建动态库,其他的内置变量还包括如下几种。
-
—BUILD_STATIC_LIBRARY: 构建静态库
-
—PREBUILT_STATIC_LIBRARY: 对已有的静态库进行包装,使其成为一个模块。
-
—PREBUILT_SHARED_LIBRARY: 对已有的静态库进行包装,使其成为一个模块。
-
—BUILD_EXECUTABLE: 构建可执行文件。
2. Application.mk
- APP_ABI := XXX ,这里的 XXX 是指不同平台,可以选填的有 x86 、mips 、armeabi、armeabi-v7a、all 等,值得一提的是,若选择 all 则会构建构建出所有平台的 so ,如果不填写该项,那么将默认构建为 armeabi 平台下的库。
- APP_STL := gnustl_static ,NDK 构建系统提供了由 Android 系统给出的最小 C++ 运行时库 (、system/lib/libstdc++.so)的 C++ 头文件。
- APP_CPPFLAGS :=-std=gnu++11 -fexceptions, 指定编译过程的 flag ,可以在该选项中开启 exception rtti 等特性,但是为了效率考虑,最好关闭 rtti。
- NDK_TOOLCHAIN_VERSION = 4.8,指定交叉工具编译链里面的版本号,这里指定使用 4.8。
- APP_PLATFORM :=android-9,指定创建的动态库的平台
- APP_OPTIM := release,该变量是可选的,用来定义 “release” 或者 “debug” ,“release” 模式是默认的,并且会生成高度优化的二进制代码;“debug” 模式生成的是未优化的二进制代码,但是可以检测出很多的 BUG, 经常用于调试阶段,也相当于在 ndk-build 指令后边直接加上参数 NDK_DEBUG=1。
构建 C/C++ Android 项目
效果:
Makefile 的方式我们只做一个了解,因为以后我们构建 C/C++ 的 Android 项目都是用 cmake 方式来构建,所以我们重点掌握 cmake 就行。
cmake
之前做 NDK 开发或者老的项目都是基于 Android.mk、Application.mk 来构建项目的,但从 AS 2.2 之后便开始采用 CMake 的方式来构建 C/C++ 项目,采用 CMake 相比与之前的 Android.mk、Application.mk 方便简单了许多。下面我们简单的来介绍下 cmake 基础语法吧。
语法基础
#1. 指定 cmake 的最小版本
cmake_minimum_required(VERSION 3.4.1)
#2. 设置项目名称
project(demo)
#3. 设置编译类型
add_executable(demo test.cpp) # 生成可执行文件
add_library(common STATIC test.cpp) # 生成静态库
add_library(common SHARED test.cpp) # 生成动态库或共享库
#4. 明确指定包含哪些源文件
add_library(demo test.cpp test1.cpp test2.cpp)
#5. 自定义搜索规则并加载文件
file(GLOB SRC_LIST “.cpp" "protocol/.cpp”)
add_library(demo ${SRC_LIST}) //加载当前目录下所有的 cpp 文件
或者
file(GLOB SRC_LIST “.cpp")
file(GLOB SRC_PROTOCOL_LIST "protocol/.cpp”)
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
或者
aux_source_directory(. SRC_LIST)//搜索当前目录下的所有.cpp文件
aux_source_directory(protocol SRC_PROTOCOL_LIST)
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
#6. 查找指定库文件
find_library(
log-lib //为 log 定义一个变量名称
log ) //ndk 下的 log 库
#7. 设置包含的目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
)
#8. 设置链接库搜索目录
link_directories(
${CMAKE_CURRENT_SOURCE_DIR}/libs
)
#9. 设置 target 需要链接的库
target_link_libraries( # 目标库
demo
目标库需要链接的库
log-lib 是上面 find_library 指定的变量名
${log-lib} )
#10. 指定链接动态库或者静态库
target_link_libraries(demo libtest.a) # 链接libtest.a
target_link_libraries(demo libtest.so) # 链接libtest.so
#11. 根据全路径链接动态静态库
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libtest.a)
target_link_libraries(demo ${CMAKE_CURRENT_SOURCE_DIR}/libs/libtest.so)
#12. 指定链接多个库
target_link_libraries(demo
${CMAKE_CURRENT_SOURCE_DIR}/libs/libtest.a
test.a
boost_thread
pthread)
常用变量
预定义变量 | 说明 |
---|---|
PROJECT_SOURCE_DIR | 工程的根目录 |
PROJECT_BINARY_DIR | 运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build |
PROJECT_NAME | 返回通过 project 命令定义的项目名称 |
CMAKE_CURRENT_SOURCE_DIR | 当前处理的 CMakeLists.txt 所在的路径 |
CMAKE_CURRENT_BINARY_DIR | target 编译目录 |
CMAKE_CURRENT_LIST_DIR | CMakeLists.txt 的完整路径 |
CMAKE_CURRENT_LIST_LINE | 当前所在的行 |
CMAKE_MODULE_PATH | 定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块 |
EXECUTABLE_OUTPUT_PATH | 重新定义目标二进制可执行文件的存放位置 |
LIBRARY_OUTPUT_PATH | 重新定义目标链接库文件的存放位置 |
构建 C/C++ Android 项目
- 以静态库构建项目
- 定义 native 接口
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary(“native-lib”);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testCmake();
}
/**
-
测试 cmake 构建程序
*/
public native static void testCmake();
}
复制代码 -
编写 cpp
// extern int main(); 这样写有坑,因为 main 方法是属于 c 的,而当前是 CPP
extern “C” { //必须这样定义
int main();
}
extern “C” JNIEXPORT void JNICALL
Java_com_devyk_cmake_1application_MainActivity_testCmake(
JNIEnv env,
jobject / this */) {
std::string hello = “Hello from C++”;
__android_log_print(ANDROID_LOG_DEBUG, “DevYK”, “main—>:%d”, main());
}
- 编写 CmakeLists.txt 文件
cmake_minimum_required(VERSION 3.4.1)
打印日志
message(“当前CMake的路径是: C M A K E S O U R C E D I R " ) m e s s a g e ( " 当前 C M A K E A N D R O I D A R C H A B I 的路径是: {CMAKE_SOURCE_DIR}") message("当前 CMAKE_ANDROID_ARCH_ABI 的路径是: CMAKESOURCEDIR")message("当前CMAKEANDROIDARCHABI的路径是:{CMAKE_ANDROID_ARCH_ABI}”)
批量引入源文件
file(GLOB allCpp *.cpp)
加入cpp源文件
add_library(
native-lib
SHARED
native-lib.cpp 替换 ${allCpp} 批量导入文件
${allCpp}
)
导入静态库
add_library(test_a STATIC IMPORTED)
开始真正的导入
set_target_properties(test_a PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libtest.a)
只能找系统的
find_library(
log-lib
log)
message(“当前的log路径在哪里啊 >>>>>>>>>>>>>>>>> ${log-lib}”)
#开始链接指定的库
target_link_libraries(
native-lib
${log-lib}
test_a
)
- app/build.gradle cmake 配置
android {
…
defaultConfig {
…
externalNativeBuild {
cmake {
// cppFlags “” // 默认包含四大平台
abiFilters ‘armeabi-v7a’//编译armeabi-v7a平台
}
}
ndk {
//过滤,只使用这个版本的库,否则默认的可是4个平台
abiFilters ‘armeabi-v7a’
}
}
…
externalNativeBuild {
cmake {
path “src/main/cpp/CMakeLists.txt” //指定 CMakeLists 路径
}
}
}
- 测试结果
- 以动态库构建项目
- 代码加载 so 库到手机中
static {
System.loadLibrary(“Test”);
System.loadLibrary(“native-lib”);
}
- so 库导入在 main/jniLibs 下
- CmakeLists.txt 配置
cmake_minimum_required(VERSION 3.4.1)
打印日志
最后
说一千道一万,不如自己去行动。要想在移动互联网的下半场是自己占有一席之地,那就得从现在开始,从今天开始,马上严格要求自己,既重视业务实现能力,也重视基础和原理。基础夯实好了,高楼才能够平地而起,稳如泰山。
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2020-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
中…(img-4SPbL2dW-1715879874533)]
[外链图片转存中…(img-YiQy6RhC-1715879874533)]
[外链图片转存中…(img-Kjd734Kg-1715879874534)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!