在本文中,我们将详细介绍[android]天气app布局练习的各个方面,并为您提供关于二的相关解答,同时,我们也将为您带来关于Android8.1源码_启动篇(二)--深入研究zygote(转Andr
在本文中,我们将详细介绍[android] 天气app布局练习的各个方面,并为您提供关于二的相关解答,同时,我们也将为您带来关于Android 8.1 源码_启动篇(二) -- 深入研究 zygote(转 Android 9.0 分析)、Android APP发布(二)发布前准备、Android BLE蓝牙配置全流程(二) 附APP源码、android FrameWork学习(二)Android系统源码调试的有用知识。
本文目录一览:- [android] 天气app布局练习(二)(安卓天气应用)
- Android 8.1 源码_启动篇(二) -- 深入研究 zygote(转 Android 9.0 分析)
- Android APP发布(二)发布前准备
- Android BLE蓝牙配置全流程(二) 附APP源码
- android FrameWork学习(二)Android系统源码调试
[android] 天气app布局练习(二)(安卓天气应用)
主要练习一下GridView
MainActivity.java
package com.example.weatherreport; import java.util.ArrayList; java.util.HashMap; java.util.List; java.util.Map; android.app.Activity; android.os.Bundle; android.widget.GridView; android.widget.SimpleAdapter; public class MainActivity extends Activity { private GridView gv_airs; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); gv_airs=(GridView) findViewById(R.id.gv_airs); makeGridView(); } /** * 组装GridView */ private makeGridView() { List<Map<String,String>> data=new ArrayList<>(); Map<String,String> item=null; item=new HashMap<>(); item.put("title","83"); item.put("desc","湿度(%)"); data.add(item); item=); data.add(item); SimpleAdapter adapter=new SimpleAdapter(this,data,R.layout.main_grid_item,new String[]{"title","desc"},1)">new int[]{R.id.tv_title,R.id.tv_desc}); gv_airs.setAdapter(adapter); } }
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height android:background="#3186D9" tools:context="${relativePackage}.${activityClass}" > ImageView android:layout_width="wrap_content" android:layout_height android:layout_marginLeft="10dp" android:layout_marginTop android:src="@drawable/icon_home" /> TextView android:layout_centerHorizontal="true"="12dp" android:text="北京" android:textColor="#FDFDFD" android:layout_alignParentRight android:layout_marginRight="@drawable/icon_more" android:id="@+id/tv_number" android:layout_width="70dp"="29"="#fff" android:textSize="50sp" android:layout_alignTop="@id/tv_number" android:layout_toRightOf="°"="#FDFDFD"="30sp" LinearLayout ="@+id/ll_weather" android:layout_below android:orientation="horizontal" > TextView android:layout_height android:layout_centerHorizontal android:text="多云" android:textColor/> =" | "="#bbb" ="空气优"/> </LinearLayoutView ="@+id/gv_airs_top"="1dp"="@id/ll_weather"="20dp" android:background="#ddd" GridView ="@+id/gv_airs"="@id/gv_airs_top"="#ddd" android:horizontalSpacing android:listSelector="@android:color/transparent" android:numColumns="3" android:verticalSpacing="1dp" ="@id/gv_airs"/> RelativeLayout>
main_grid_item.xml
<?xml version="1.0" encoding="utf-8"?> android:layout_marginLeft android:paddingTop="25dp" android:paddingBottom android:layout_centerInParent android:gravity="center"="vertical" ="@+id/tv_desc" android:layout_width="湿度(%)" android:textSize="12sp" ="@+id/tv_title"="83"="22sp" > >
Android 8.1 源码_启动篇(二) -- 深入研究 zygote(转 Android 9.0 分析)
前言
在Android中,zygote是整个系统创建新进程的核心进程。zygote进程在内部会先启动Dalvik虚拟机,继而加载一些必要的系统资源和系统类,最后进入一种监听状态。在之后的运作中,当其他系统模块(比如 AMS)希望创建新进程时,只需向zygote进程发出请求,zygote进程监听到该请求后,会相应地fork出新的进程,于是这个新进程在初生之时,就先天具有了自己的Dalvik虚拟机以及系统资源。
开篇
核心源码
关键类 | 路径 |
---|---|
<font color=#D15FEE>init.rc</font> | system/core/rootdir/init.rc |
<font color=#D15FEE>init.cpp</font> | system/core/init/init.cpp |
<font color=#D15FEE>init.zygote64.rc</font> | system/core/rootdir/init.zygote64.rc |
<font color=#D15FEE>builtins.cpp</font> | system/core/init/builtins.cpp |
<font color=#D15FEE>service.cpp</font> | system/core/init/service.cpp |
<font color=#D15FEE>app_main.cpp</font> | frameworks/base/cmds/app_process/app_main.cpp |
<font color=#D15FEE>AndroidRuntime.cpp</font> | frameworks/base/core/jni/AndroidRuntime.cpp |
<font color=#D15FEE>JniInvocation.cpp</font> | libnativehelper/JniInvocation.cpp |
<font color=#D15FEE>ZygoteInit.java</font> | frameworks/base/core/java/com/android/internal/os/ZygoteInit.java |
<font color=#D15FEE>ZygoteServer.java</font> | frameworks/base/core/java/com/android/internal/os/ZygoteServer.java |
Zygote简介
在Android系统中,JavaVM(Java虚拟机)、应用程序进程以及运行系统的关键服务的SystemServer进程都是由Zygote进程来创建的,我们也将它称为孵化器。它通过fock(复制进程)的形式来创建应用程序进程和SystemServer进程,由于Zygote进程在启动时会创建JavaVM,因此通过fock而创建的应用程序进程和SystemServer进程可以在内部获取一个JavaVM的实例拷贝。
Read The Fucking Code
Zygote触发
在分析init进程时,我们知道init进程启动后,会解析init.rc文件,然后创建和加载service字段指定的进程。zygote进程就是以这种方式,被init进程加载的。
在system/core/rootdir/init.rc的开始部分,可以看到:
import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc // ${ro.zygote}由厂商定义,与平台相关
on early-init
# Set init and its forked children''s oom_adj.
write /proc/1/oom_score_adj -1000
init.zygoteXX.rc
从之前分析的init篇中我们知道,在不同的平台(32、64及64_32)上,init.rc将包含不同的zygote.rc文件。在system/core/rootdir目录下,有init.zygote32_64.rc、init.zyote64.rc、 init.zyote32.rc、init.zygote64_32.rc。
✨ <font color=#87CEFA>init.zygote32.rc</font>:zygote 进程对应的执行程序是 app_process (纯 32bit 模式) ✨ <font color=#87CEFA>init.zygote64.rc</font>:zygote 进程对应的执行程序是 app_process64 (纯 64bit 模式) ✨ <font color=#87CEFA>init.zygote32_64.rc</font>:启动两个 zygote 进程 (名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process32 (主模式)、app_process64 ✨ <font color=#87CEFA>init.zygote64_32.rc</font>:启动两个 zygote 进程 (名为 zygote 和 zygote_secondary),对应的执行程序分别是 app_process64 (主模式)、app_process32
为什么要定义这么多种情况呢?直接定义一个不就好了,这主要是因为Android 5.0以后开始支持64位程序,为了兼容32位和64位才这样定义。不同的zygote.rc内容大致相同,主要区别体现在启动的是32位,还是64位的进程。init.zygote32_64.rc和init.zygote64_32.rc会启动两个进程,且存在主次之分。
这里拿64位处理器为例,init.zygote64_32.rc的代码如下所示:
// 进程名称是zygote,运行的二进制文件在/system/bin/app_process64
// 启动参数是 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
class main
priority -20
user root
group root readproc
socket zygote stream 660 root system // 创建一个socket,名字叫zygote,以tcp形式
onrestart write /sys/android_power/request_state wake // onrestart 指当进程重启时执行后面的命令
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks // 创建子进程时,向 /dev/cpuset/foreground/tasks 写入pid
// 另一个service ,名字 zygote_secondary
service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload
class main
priority -20
user root
group root readproc
socket zygote_secondary stream 660 root system
onrestart restart zygote
writepid /dev/cpuset/foreground/tasks
start zygote
定义了service,肯定有地方调用 start zygote。在之前init解析的博客中,我们分析过init进程的启动。init进程启动的最后,会产生”late-init”事件。
// Don''t mount filesystems or start core system services in charger mode.
std::string bootmode = GetProperty("ro.bootmode", "");
if (bootmode == "charger") {
am.QueueEventTrigger("charger");
} else {
am.QueueEventTrigger("late-init");
}
对应于init.rc配置文件中,我们找到如下代码:
# Mount filesystems and start core system services.
on late-init
trigger early-fs
... ...
# Now we can start zygote for devices with file based encryption
trigger zygote-start // 触发了zygote-start事件后,就会启动zygote进程
... ...
对应于init.rc配置文件中,我们找到如下代码:
# It is recommended to put unnecessary data/ initialization from post-fs-data
# to start-zygote in device''s init.rc to unblock zygote start.
on zygote-start && property:ro.crypto.state=unencrypted
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start netd // start对应的映射关系定义于system/core/init/builtins.cpp中
start zygote // 调用start对应的处理函数,启动名为zygote的服务(传入前文init.zygote.rc中定义的参数)
start zygote_secondary
on zygote-start && property:ro.crypto.state=unsupported
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start netd
start zygote
start zygote_secondary
on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
# A/B update verifier that marks a successful boot.
exec_start update_verifier_nonencrypted
start netd
start zygote
start zygote_secondary
start命令有一个对应的执行函数do_start,定义在platform/system/core/init/builtins.cpp中
const BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const {
constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
// clang-format off
static const Map builtin_functions = {
... ...
{"start", {1, 1, do_start}},
... ...
};
// clang-format on
return builtin_functions;
}
我们来看下do_start():
static int do_start(const std::vector<std::string>& args) {
Service* svc = ServiceManager::GetInstance().FindServiceByName(args[1]); // 找到zygote service对应信息
if (!svc) {
LOG(ERROR) << "do_start: Service " << args[1] << " not found";
return -1;
}
if (!svc->Start()) // 启动对应的进程
return -1;
return 0;
}
do_start首先是通过FindServiceByName去service数组中遍历,根据名字匹配出对应的service,然后调用service的Start函数。
最后,我们来看看service.cpp中定义Start函数:
bool Service::Start() {
... ...
pid_t pid = -1;
if (namespace_flags_) {
pid = clone(nullptr, nullptr, namespace_flags_ | SIGCHLD, nullptr);
} else {
pid = fork(); // 从init进程中,fork出zygote进程
}
... ...
}
Start函数主要是fork出一个新进程,然后执行service对应的二进制文件,并将参数传递进去。那么下面我们以init.zygote64.rc为例进行分析。
app_process
从上面我们分析的init.zygote64.rc可以看出,zygote64启动文件的地址为app_process64。app_process64对应的代码定义在frameworks/base/cmds/app_process中,
我们来看看对应的Android.mk: frameworks/base/cmds/app_process
LOCAL_PATH:= $(call my-dir)
... ...
app_process_src_files := \
app_main.cpp \
... ...
LOCAL_MODULE:= app_process
LOCAL_MULTILIB := both
LOCAL_MODULE_STEM_32 := app_process32
LOCAL_MODULE_STEM_64 := app_process64
其实不管是app_process、app_process32还是app_process64,对应的源文件都是app_main.cpp。
接下来我们就看看app_process对应的main函数,该函数定义于app_main.cpp中。
在app_main.cpp的main函数中,主要做的事情就是参数解析. 这个函数有两种启动模式:
✨ 一种是zygote模式,也就是初始化zygote进程,传递的参数有--start-system-server --socket-name=zygote,前者表示启动SystemServer,后者指定socket的名称(Zygote64_32)。 ✨ 一种是application模式,也就是启动普通应用程序,传递的参数有class名字以及class带的参数。
两者最终都是调用AppRuntime对象的start函数,加载ZygoteInit或RuntimeInit两个Java类,并将之前整理的参数传入进去。
我们这里暂时只讲解ZygoteInit的加载流程。
int main(int argc, char* const argv[])
{
// 将参数argv放到argv_String字符串中,然后打印出来
// 之前start zygote传入的参数是 -Xzygote /system/bin --zygote --start-system-server
if (!LOG_NDEBUG) {
String8 argv_String;
for (int i = 0; i < argc; ++i) {
argv_String.append("\"");
argv_String.append(argv[i]);
argv_String.append("\" ");
}
ALOGV("app_process main with argv: %s", argv_String.string());
}
// AppRuntime定义于app_main.cpp中,继承自AndroidRuntime
// 就是对Android运行环境的一种抽象,类似于java虚拟机对Java程序的作用
AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
// Process command line arguments
// ignore argv[0]
argc--;
argv++;
// 这两个参数是Java程序需要依赖的Jar包,相当于import
const char* spaced_commands[] = { "-cp", "-classpath" };
// Allow "spaced commands" to be succeeded by exactly 1 argument (regardless of -s).
bool known_command = false;
int i;
// 找到解析参数的起点
for (i = 0; i < argc; i++) {
// 将spaced_commands中的参数额外加入VM
if (known_command == true) {
runtime.addOption(strdup(argv[i]));
// The static analyzer gets upset that we don''t ever free the above
// string. Since the allocation is from main, leaking it doesn''t seem
// problematic. NOLINTNEXTLINE
ALOGV("app_process main add known option ''%s''", argv[i]);
known_command = false;
continue;
}
for (int j = 0;
j < static_cast<int>(sizeof(spaced_commands) / sizeof(spaced_commands[0]));
++j) {
// 比较参数是否是spaced_commands中的参数
if (strcmp(argv[i], spaced_commands[j]) == 0) {
known_command = true;
ALOGV("app_process main found known command ''%s''", argv[i]);
}
}
// 如果参数第一个字符是''-'',直接跳出循环,之前传入的第一个参数是 -Xzygote,所以执行到这儿就跳出了
if (argv[i][0] != ''-'') {
break;
}
if (argv[i][1] == ''-'' && argv[i][2] == 0) {
++i; // Skip --.
break;
}
runtime.addOption(strdup(argv[i]));
// The static analyzer gets upset that we don''t ever free the above
// string. Since the allocation is from main, leaking it doesn''t seem
// problematic. NOLINTNEXTLINE
ALOGV("app_process main add option ''%s''", argv[i]);
}
// Parse runtime arguments. Stop at first unrecognized option.
// 从这里其实可以看出,通过app_main可以启动zygote、system-server及普通apk进程
// 这个可以通过init.rc来配置
bool zygote = false;
bool startSystemServer = false;
bool application = false;
String8 niceName; // app_process的名称改为zygote
String8 className; // 启动apk进程时,对应的类名
++i; // Skip unused "parent dir" argument.
// 跳过一个参数,之前跳过了-Xzygote,这里继续跳过 /system/bin ,也就是所谓的 "parent dir"
while (i < argc) { // 开始解析输入参数
const char* arg = argv[i++];
if (strcmp(arg, "--zygote") == 0) { // 表示是zygote启动模式
zygote = true;
niceName = ZYGOTE_NICE_NAME; // 这个值根据平台可能是zygote64或zygote
} else if (strcmp(arg, "--start-system-server") == 0) {
startSystemServer = true; // init.zygote.rc中定义了该字段,启动zygote后会启动system-server
} else if (strcmp(arg, "--application") == 0) {
application = true; // 表示是application启动模式,也就是普通应用程序
} else if (strncmp(arg, "--nice-name=", 12) == 0) {
niceName.setTo(arg + 12); // 进程别名,可以自己指定进程名
} else if (strncmp(arg, "--", 2) != 0) {
className.setTo(arg); // 与--application配置,启动指定的类,application启动的class
break;
} else {
--i;
break;
}
}
// 准备参数
Vector<String8> args;
if (!className.isEmpty()) { // className不为空,说明是application启动模式
// We''re not in zygote mode, the only argument we need to pass
// to RuntimeInit is the application argument.
//
// The Remainder of args get passed to startup class main(). Make
// copies of them before we overwrite them with the process name.
args.add(application ? String8("application") : String8("tool"));
runtime.setClassNameAndArgs(className, argc - i, argv + i); // 将className和参数设置给runtime
... ...
} else { // zygote启动模式
// We''re in zygote mode.
maybeCreateDalvikCache(); // 创建Dalvik的缓存目录并定义权限
if (startSystemServer) { // 增加start-system-server参数,默认启动zygote后,就会启动system_server
args.add(String8("start-system-server"));
}
char prop[PROP_VALUE_MAX]; // 获取平台对应的abi信息
if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) {
LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.",
ABI_LIST_PROPERTY);
return 11;
}
String8 abiFlag("--abi-list="); // 参数需要制定abi
abiFlag.append(prop);
args.add(abiFlag); // 加入--abi-list=参数
// In zygote mode, pass all remaining arguments to the zygote
// main() method.
for (; i < argc; ++i) {
args.add(String8(argv[i])); // 将剩下的参数加入args
}
}
if (!niceName.isEmpty()) { // 将app_process的进程名,替换为niceName
runtime.setArgv0(niceName.string(), true /* setProcName */);
}
if (zygote) { // 调用Runtime的start函数, 启动ZygoteInit
runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
} else if (className) { // 启动zygote没有进入这个分支
// 但这个分支说明,通过配置init.rc文件,其实是可以不通过zygote来启动一个进程
// 如果是application启动模式,则加载RuntimeInit
runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
} else {
// error情况
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
}
}
AndroidRuntime
由于AppRuntime继承自AndroidRuntime,且没有重写start方法,因此zygote的流程进入到了AndroidRuntime.cpp。
接下来,我们来看看AndroidRuntime的start函数的流程。
创建Java虚拟机
/*
* Start the Android runtime. This involves starting the virtual machine
* and calling the "static void main(String[] args)" method in the class
* named by "className".
*
* Passes the main function two arguments, the class name and the specified
* options string.
*/
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
... ... // 打印一些日志,获取ANDROID_ROOT环境变量
/* start the virtual machine */
JniInvocation jni_invocation;
jni_invocation.Init(NULL); // 初始化JNI,加载libart.so
JNIEnv* env;
// 创建虚拟机,其中大多数参数由系统属性决定
// 最终,startVm利用JNI_CreateJavaVM创建出虚拟机
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
// 回调AppRuntime的onVmCreated函数
// 对于zygote进程的启动流程而言,无实际操作,表示虚拟创建完成,但是里面是空实现
onVmCreated(env);
... ...
}
这边我们跟一下jni_invocation.Init():libnativehelper/JniInvocation.cpp
Init函数主要作用是初始化JNI,具体工作是首先通过dlopen加载libart.so获得其句柄,然后调用dlsym从libart.so中找到JNI_GetDefaultJavaVMInitArgs、JNI_CreateJavaVM、JNI_GetCreatedJavaVMs三个函数地址,赋值给对应成员属性,这三个函数会在后续虚拟机创建中调用。
bool JniInvocation::Init(const char* library) {
#ifdef __ANDROID__
char buffer[PROP_VALUE_MAX];
#else
char* buffer = NULL;
#endif
library = GetLibrary(library, buffer); // 默认返回 libart.so
// Load with RTLD_NODELETE in order to ensure that libart.so is not unmapped when it is closed.
// This is due to the fact that it is possible that some threads might have yet to finish
// exiting even after JNI_DeleteJavaVM returns, which can lead to segfaults if the library is
// unloaded.
const int kDlopenFlags = RTLD_NOW | RTLD_NODELETE;
/*
* 1.dlopen功能是以指定模式打开指定的动态链接库文件,并返回一个句柄
* 2.RTLD_NOW表示需要在dlopen返回前,解析出所有未定义符号,如果解析不出来,在dlopen会返回NULL
* 3.RTLD_NODELETE表示在dlclose()期间不卸载库,并且在以后使用dlopen()重新加载库时不初始化库中的静态变量
*/
handle_ = dlopen(library, kDlopenFlags); // 获取libart.so的句柄
if (handle_ == NULL) { // 获取失败打印错误日志并尝试再次打开libart.so
if (strcmp(library, kLibraryFallback) == 0) {
// Nothing else to try.
ALOGE("Failed to dlopen %s: %s", library, dlerror());
return false;
}
// Note that this is enough to get something like the zygote
// running, we can''t property_set here to fix this for the future
// because we are root and not the system user. See
// RuntimeInit.commonInit for where we fix up the property to
// avoid future fallbacks. http://b/11463182
ALOGW("Falling back from %s to %s after dlopen error: %s",
library, kLibraryFallback, dlerror());
library = kLibraryFallback;
handle_ = dlopen(library, kDlopenFlags);
if (handle_ == NULL) {
ALOGE("Failed to dlopen %s: %s", library, dlerror());
return false;
}
}
/*
* 1.FindSymbol函数内部实际调用的是dlsym
* 2.dlsym作用是根据 动态链接库 操作句柄(handle)与符号(symbol),返回符号对应的地址
* 3.这里实际就是从libart.so中将JNI_GetDefaultJavaVMInitArgs等对应的地址存入&JNI_GetDefaultJavaVMInitArgs_中
*/
if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetDefaultJavaVMInitArgs_),
"JNI_GetDefaultJavaVMInitArgs")) {
return false;
}
if (!FindSymbol(reinterpret_cast<void**>(&JNI_CreateJavaVM_),
"JNI_CreateJavaVM")) {
return false;
}
if (!FindSymbol(reinterpret_cast<void**>(&JNI_GetCreatedJavaVMs_),
"JNI_GetCreatedJavaVMs")) {
return false;
}
return true;
}
其次,我们再跟一下startVm():
这个函数特别长,但是里面做的事情很单一,其实就是从各种系统属性中读取一些参数,然后通过addOption设置到AndroidRuntime的mOptions数组中存起来,另外就是调用之前从libart.so中找到JNI_CreateJavaVM函数,并将这些参数传入,由于本篇主要讲zygote启动流程,因此关于虚拟机的实现就不深入探究了。
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
{
JavaVMInitArgs initArgs;
char propBuf[PROPERTY_VALUE_MAX];
char stackTraceFileBuf[sizeof("-Xstacktracefile:")-1 + PROPERTY_VALUE_MAX];
char jniOptsBuf[sizeof("-Xjniopts:")-1 + PROPERTY_VALUE_MAX];
... ...
/* route exit() to our handler */
addOption("exit", (void*) runtime_exit); // 将参数放入mOptions数组中
... ...
initArgs.version = JNI_VERSION_1_4;
initArgs.options = mOptions.editArray(); // 将mOptions赋值给initArgs
initArgs.nOptions = mOptions.size();
initArgs.ignoreUnrecognized = JNI_FALSE;
/*
* Initialize the VM.
*
* The JavaVM* is essentially per-process, and the JNIEnv* is per-thread.
* If this call succeeds, the VM is ready, and we can start issuing
* JNI calls.
*/
if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) { // 调用libart.so的JNI_CreateJavaVM函数
ALOGE("JNI_CreateJavaVM failed\n");
return -1;
}
return 0;
}
注册JNI函数
我们回到AndroidRuntime的start函数。初始化JVM后,接下来就会调用startReg函数。
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
... ...
/* 01. 创建Java虚拟机*/
/*
* Register android functions.
*/
if (startReg(env) < 0) { // 注册JNI函数
ALOGE("Unable to register all android natives\n");
return;
}
... ...
}
startReg首先是设置了Android创建线程的处理函数,然后创建了一个200容量的局部引用作用域,用于确保不会出现OutOfMemoryException,最后就是调用register_jni_procs进行JNI注册。
我们跟进startReg():
/*
* Register android native functions with the VM.
*/
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
ATRACE_NAME("RegisterAndroidNatives");
/*
* This hook causes all future threads created in this process to be
* attached to the JavaVM. (This needs to go away in favor of JNI
* Attach calls.)
*/
// 定义Android创建线程的func:javaCreateThreadEtc,这个函数内部是通过Linux的clone来创建线程的
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
ALOGV("--- registering native functions ---\n");
/*
* Every "register" function calls one or more things that return
* a local reference (e.g. FindClass). Because we haven''t really
* started the VM yet, they''re all getting stored in the base frame
* and never released. Use Push/Pop to manage the storage.
*/
env->PushLocalFrame(200); // 创建一个200容量的局部引用作用域,这个局部引用其实就是局部变量
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) { // 注册JNI函数
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL); // 释放局部引用作用域
//createJavaThread("fubar", quickTest, (void*) "hello");
return 0;
}
从上述代码可以看出,startReg函数中主要是通过register_jni_procs来注册JNI函数。其中,gRegJNI是一个全局数组,该数组的定义如下:
static const RegJNIRec gRegJNI[] = { // 里面就是一堆的函数指针
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
REG_JNI(register_android_util_Log),
... ...
};
我们挑一个register_com_android_internal_os_ZygoteInit_nativeZygoteInit,这实际上是自定义JNI函数并进行动态注册的标准写法, 内部是调用JNI的RegisterNatives,这样注册后,Java类ZygoteInit的native方法nativeZygoteInit就会调用com_android_internal_os_ZygoteInit_nativeZygoteInit函数。
int register_com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env)
{
const JNINativeMethod methods[] = {
{ "nativeZygoteInit", "()V",
(void*) com_android_internal_os_ZygoteInit_nativeZygoteInit },
};
return jniRegisterNativeMethods(env, "com/android/internal/os/ZygoteInit",
methods, NELEM(methods));
}
REG_JNI对应的宏定义及RegJNIRec结构体的定义为:
#ifdef NDEBUG
#define REG_JNI(name) { name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
};
#else
#define REG_JNI(name) { name, #name }
struct RegJNIRec {
int (*mProc)(JNIEnv*);
const char* mName;
};
#endif
根据宏定义可以看出,宏REG_JNI将得到函数名;定义RegJNIRec数组时,函数名被赋值给RegJNIRec结构体,于是每个函数名被强行转换为函数指针。 因此,register_jni_procs的参数就是一个函数指针数组,数组的大小和JNIEnv。
我们来跟进一下register_jni_procs函数:
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
for (size_t i = 0; i < count; i++) {
if (array[i].mProc(env) < 0) { // 调用mProc
#ifndef NDEBUG
ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
return -1;
}
}
return 0;
}
结合前面的分析,容易知道register_jni_procs函数,实际上就是调用函数指针(mProc)对应的函数,以进行实际的JNI函数注册。
反射启动ZygoteInit
继续分析AndroidRuntime.cpp的start函数:
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
... ...
/* 01. 创建Java虚拟机*/
/* 02. 注册JNI函数 */
/*
* Start VM. This thread becomes the main thread of the VM, and will
* not return until the VM exits.
*/
// 替换string为实际路径
// 例如:将 "com.android.internal.os.ZygoteInit" 替换为 "com/android/internal/os/ZygoteInit"
char* slashClassName = toSlashClassName(className != NULL ? className : "");
jclass startClass = env->FindClass(slashClassName); // 找到class文件
if (startClass == NULL) {
ALOGE("JavaVM unable to locate class ''%s''\n", slashClassName);
/* keep going */
} else {
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V"); // 通过反射找到ZygoteInit的main函数
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in ''%s''\n", className);
/* keep going */
} else {
env->CallStaticVoidMethod(startClass, startMeth, strArray); // 调用ZygoteInit的main函数
... ...
}
}
free(slashClassName);
ALOGD("Shutting down VM\n");
if (mJavaVM->DetachCurrentThread() != JNI_OK) // 退出当前线程
ALOGW("Warning: unable to detach main thread\n");
if (mJavaVM->DestroyJavaVM() != 0) // 创建一个线程,该线程会等待所有子线程结束后关闭虚拟机
ALOGW("Warning: VM did not shut down cleanly\n");
}
可以看到,在AndroidRuntime的最后,将通过反射调用ZygoteInit的main函数。至此,<font color=#87CEFA>zygote进程进入了java世界</font>。
其实我们仔细想一想,就会觉得zygote的整个流程实际上是非常符合实际情况的。 ✨✨ 在Android中,每个进程都运行在对应的虚拟机上,因此zygote首先就负责创建出虚拟机。 ✨✨ 然后,为了反射调用java代码,必须有对应的JNI函数,于是zygote进行了JNI函数的注册。 ✨✨ 当一切准备妥当后,zygote进程才进入到了java世界。
ZygoteInit
现在我们跟进ZygoteInit.java的main函数。
public static void main(String argv[]) {
//创建ZygoteServer对象
ZygoteServer zygoteServer = new ZygoteServer();
// Mark zygote start. This ensures that thread creation will throw
// an error.
// 调用native函数,确保当前没有其它线程在运行
// 主要还是处于安全的考虑
ZygoteHooks.startZygoteNoThreadCreation();
// Zygote goes into its own process group.
try {
Os.setpgid(0, 0);
} catch (ErrnoException ex) {
throw new RuntimeException("Failed to setpgid(0,0)", ex);
}
final Runnable caller;
try {
... ...
RuntimeInit.enableDdms();
boolean startSystemServer = false;
String socketName = "zygote";
String abiList = null;
boolean enableLazyPreload = false;
// 解析参数,得到上述变量的值
for (int i = 1; i < argv.length; i++) {
if ("start-system-server".equals(argv[i])) {
startSystemServer = true;
} else if ("--enable-lazy-preload".equals(argv[i])) {
enableLazyPreload = true;
} else if (argv[i].startsWith(ABI_LIST_ARG)) {
abiList = argv[i].substring(ABI_LIST_ARG.length());
} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
socketName = argv[i].substring(SOCKET_NAME_ARG.length());
} else {
throw new RuntimeException("Unknown command line argument: " + argv[i]);
}
}
if (abiList == null) {
throw new RuntimeException("No ABI list supplied.");
}
zygoteServer.registerServerSocket(socketName); // 注册server socket
// In some configurations, we avoid preloading resources and classes eagerly.
// In such cases, we will preload things prior to our first fork.
if (!enableLazyPreload) {
... ...
preload(bootTimingsTraceLog); // 默认情况,预加载信息
... ...
} else {
// 如注释,延迟预加载
// 变更Zygote进程优先级为NORMAL级别
// 第一次fork时才会preload
Zygote.resetNicePriority();
}
// Do an initial gc to clean up after startup
bootTimingsTraceLog.traceBegin("PostZygoteInitGC");
gcAndFinalize(); // 如果预加载了,很有必要GC一波
bootTimingsTraceLog.traceEnd(); // PostZygoteInitGC
... ...
// Zygote process unmounts root storage spaces.
Zygote.nativeUnmountStorageOnInit();
// Set seccomp policy
// 加载seccomp的过滤规则
// 所有 Android 软件都使用系统调用(简称为 syscall)与 Linux 内核进行通信
// 内核提供许多特定于设备和SOC的系统调用,让用户空间进程(包括应用)可以直接与内核进行交互
// 不过,其中许多系统调用Android未予使用或未予正式支持
// 通过seccomp,Android可使应用软件无法访问未使用的内核系统调用
// 由于应用无法访问这些系统调用,因此,它们不会被潜在的有害应用利用
// 该过滤器安装到zygote进程中,由于所有Android应用均衍生自该进程
// 因而会影响到所有应用
Seccomp.setPolicy();
/// M: Added for BOOTPROF
addBootEvent("Zygote:Preload End");
/// @}
ZygoteHooks.stopZygoteNoThreadCreation(); // 允许有其它线程了
if (startSystemServer) {
Runnable r = forkSystemServer(abiList, socketName, zygoteServer); // fork出system server
// {@code r == null} in the parent (zygote) process, and {@code r != null} in the
// child (system_server) process.
if (r != null) {
r.run();
return;
}
}
Log.i(TAG, "Accepting command socket connections");
// The select loop returns early in the child process after a fork and
// loops forever in the zygote.
caller = zygoteServer.runSelectLoop(abiList); // zygote进程进入无限循环,处理请求
} catch (Throwable ex) {
... ...
} finally {
zygoteServer.closeServerSocket();
}
// We''re in the child process and have exited the select loop. Proceed to execute the
// command.
if (caller != null) {
caller.run();
}
}
上面是ZygoteInit的main函数的主干部分,除了安全相关的内容外,最主要的工作就是注册server socket、预加载、启动system server及进入无限循环处理请求消息。
接下来我们分四部分分别讨论!
创建server socket
Android O将server socket相关的工作抽象到ZygoteServer.java中了。我们来看看其中的registerZygoteSocket函数:
/**
* Registers a server socket for zygote command connections
*
* @throws RuntimeException when open fails
*/
void registerServerSocket(String socketName) {
if (mServerSocket == null) {
int fileDesc;
// ANDROID_SOCKET_PREFIX为"ANDROID_SOCKET_"
// 此处的socket name,就是zygote
final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
try {
// 还记得么?在init.zygote.rc被加载时,指定了名为zygote的socket
// 在进程被创建时,就会创建对应的文件描述符,并加入到环境变量中
// 因此,此时可以取出对应的环境变量
String env = System.getenv(fullSocketName);
fileDesc = Integer.parseInt(env);
} catch (RuntimeException ex) {
throw new RuntimeException(fullSocketName + " unset or invalid", ex);
}
try {
FileDescriptor fd = new FileDescriptor();
fd.setInt$(fileDesc); // 获取zygote socket的文件描述符
mServerSocket = new LocalServerSocket(fd); // 将socket包装成一个server socket
} catch (IOException ex) {
throw new RuntimeException(
"Error binding to local socket ''" + fileDesc + "''", ex);
}
}
}
我们跟踪LocalServerSocket():
public LocalServerSocket(String name) throws IOException
{
impl = new LocalSocketImpl();
impl.create(LocalSocket.SOCKET_STREAM); // 创建SOCKET_STREAM类型的AF_UNIX socket
localAddress = new LocalSocketAddress(name);
impl.bind(localAddress); // 绑定到指定地址
impl.listen(LISTEN_BACKLOG); // 开始监听
}
预加载
我们看看预加载的内容:
static void preload(TimingsTraceLog bootTimingsTraceLog) {
... ...
beginIcuCachePinning(); // Pin ICU Data, 获取字符集转换资源等
... ...
preloadClasses(); // 读取文件system/etc/preloaded-classes,然后通过反射加载对应的类
// 一般由厂商来定义,有时需要加载数千个类,启动慢的原因之一
... ...
preloadResources(); // 负责加载一些常用的系统资源
... ...
nativePreloadAppProcessHALs();
... ...
preloadOpenGL(); // 图形相关
... ...
preloadSharedLibraries(); // 一些必要库
preloadTextResources(); // 语言相关的字符信息
// Ask the WebViewFactory to do any initialization that must run in the zygote process,
// for memory sharing purposes.
WebViewFactory.prepareWebViewInZygote();
endIcuCachePinning();
warmUpJcaProviders(); // 安全相关的
Log.d(TAG, "end preload");
sPreloadComplete = true;
}
为了让系统实际运行时更加流畅,在zygote启动时候,调用preload函数进行了一些预加载操作。Android 通过zygote fork的方式创建子进程。zygote进程预加载这些类和资源,在fork子进程时,仅需要做一个复制即可。 这样可以节约子进程的启动时间。同时,根据fork的copy-on-write机制可知,有些类如果不做改变,甚至都不用复制,子进程可以和父进程共享这部分数据,从而省去不少内存的占用。
启动SystemServer进程
再来看看启动System Server的流程:
/**
* Prepare the arguments and forks for the system server process.
*
* Returns an {@code Runnable} that provides an entrypoint into system_server code in the
* child process, and {@code null} in the parent.
*/
private static Runnable forkSystemServer(String abiList, String socketName,
ZygoteServer zygoteServer) {
long capabilities = posixCapabilitiesAsBits(
OsConstants.CAP_IPC_LOCK,
OsConstants.CAP_KILL,
OsConstants.CAP_NET_ADMIN,
OsConstants.CAP_NET_BIND_SERVICE,
OsConstants.CAP_NET_BROADCAST,
OsConstants.CAP_NET_RAW,
OsConstants.CAP_SYS_MODULE,
OsConstants.CAP_SYS_NICE,
OsConstants.CAP_SYS_PTRACE,
OsConstants.CAP_SYS_TIME,
OsConstants.CAP_SYS_TTY_CONFIG,
OsConstants.CAP_WAKE_ALARM
);
/* Containers run without this capability, so avoid setting it in that case */
if (!SystemProperties.getBoolean(PROPERTY_RUNNING_IN_CONTAINER, false)) {
capabilities |= posixCapabilitiesAsBits(OsConstants.CAP_BLOCK_SUSPEND);
}
/* Hardcoded command line to start the system server */
String args[] = {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,1032,3001,3002,3003,3006,3007,3009,3010",
"--capabilities=" + capabilities + "," + capabilities,
"--nice-name=system_server",
"--runtime-args",
"com.android.server.SystemServer",
};
ZygoteConnection.Arguments parsedArgs = null;
int pid;
try {
parsedArgs = new ZygoteConnection.Arguments(args); // 将上面准备的参数,按照ZygoteConnection的风格进行封装
ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
/* Request to fork the system server process */
pid = Zygote.forkSystemServer( // 通过fork"分裂"出system_server
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.debugFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
/* For child process */
if (pid == 0) {
// 处理32_64和64_32的情况
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
// fork时会copy socket,system server需要主动关闭
zygoteServer.closeServerSocket();
// system server进程处理自己的工作
return handleSystemServerProcess(parsedArgs);
}
return null;
}
处理请求信息
创建出SystemServer进程后,zygote进程调用ZygoteServer中的函数runSelectLoop,处理server socket收到的命令。
/**
* Runs the zygote process''s select loop. Accepts new connections as
* they happen, and reads commands from connections one spawn-request''s
* worth at a time.
*/
Runnable runSelectLoop(String abiList) {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
// 首先将server socket加入到fds
fds.add(mServerSocket.getFileDescriptor());
peers.add(null);
while (true) {
// 每次循环,都重新创建需要监听的pollFds
StructPollfd[] pollFds = new StructPollfd[fds.size()];
for (int i = 0; i < pollFds.length; ++i) {
pollFds[i] = new StructPollfd();
pollFds[i].fd = fds.get(i);
// 关注事件到来
pollFds[i].events = (short) POLLIN;
}
try {
// 等待事件到来
Os.poll(pollFds, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
// 注意这里是倒序的,即优先处理已建立链接的信息,后处理新建链接的请求
for (int i = pollFds.length - 1; i >= 0; --i) {
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
// server socket最先加入fds, 因此这里是server socket收到数据
if (i == 0) {
// 收到新的建立通信的请求,建立通信连接
ZygoteConnection newPeer = acceptCommandPeer(abiList);
// 加入到peers和fds, 即下一次也开始监听
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
//其它通信连接收到数据
... ...
}
}
}
}
从上面代码可知,初始时fds中仅有server socket,因此当有数据到来时,将执行i等于0的分支。此时,显然是需要创建新的通信连接,因此acceptCommandPeer将被调用。
我们看看acceptCommandPeer函数:
/**
* Waits for and accepts a single command connection. Throws
* RuntimeException on failure.
*/
private ZygoteConnection acceptCommandPeer(String abiList) {
try {
// socket编程中,accept()调用主要用在基于连接的套接字类型,比如SOCK_STREAM和SOCK_SEQPACKET
// 它提取出所监听套接字的等待连接队列中第一个连接请求,创建一个新的套接字,并返回指向该套接字的文件描述符
// 新建立的套接字不在监听状态,原来所监听的套接字的状态也不受accept()调用的影响
return createNewConnection(mServerSocket.accept(), abiList);
} catch (IOException ex) {
throw new RuntimeException(
"IOException during accept()", ex);
}
}
protected ZygoteConnection createNewConnection(LocalSocket socket, String abiList)
throws IOException {
return new ZygoteConnection(socket, abiList);
}
从上面的代码,可以看出acceptCommandPeer调用了server socket的accpet函数。于是当新的连接建立时,zygote将会创建出一个新的socket与其通信,并将该socket加入到fds中。因此,一旦通信连接建立后,fds中将会包含有多个socket。
当poll监听到这一组sockets上有数据到来时,就会从阻塞中恢复。于是,我们需要判断到底是哪个socket收到了数据。
在runSelectLoop中采用倒序的方式轮询。由于server socket第一个被加入到fds,因此最后轮询到的socket才需要处理新建连接的操作;其它socket收到数据时,仅需要调用zygoteConnection的runonce函数执行数据对应的操作。若一个连接处理完所有对应消息后,该连接对应的socket和连接等将被移除。
完结
Zygote启动流程到此结束,Zygote进程共做了如下几件事: 1. 创建AppRuntime并调用其start方法,启动Zygote进程。 2. 创建JavaVM并为JavaVM注册JNI. 3. 通过JNI调用ZygoteInit的main函数进入Zygote的Java框架层。 4. 通过registerZygoteSocket函数创建服务端Socket,预加载类和资源,并通过runSelectLoop函数等待如ActivityManagerService等的请求。 5. 启动SystemServer进程。
参考Blog
01. https://blog.csdn.net/tfygg/article/details/52086621 02. https://www.jianshu.com/p/cbc6b84aee08 03. https://www.jianshu.com/p/ab9b83a77af6
Android APP发布(二)发布前准备
原文链接:https://developer.android.google.cn/studio/publish/preparing#%E9%80%9A%E8%BF%87-android-studio-%E8%BF%9B%E8%A1%8C%E6%9E%84%E5%BB%BA目录
简介
收集资料和资源
配置要发布的应用
构建要发布的应用
通过 Android Studio 进行构建
准备外部服务器和资源
测试要发布的应用
在向用户分发 Android 应用之前,您必须做好发布准备。对于所有 Android 应用,准备流程都是一项必须执行的开发任务,也是发布流程的第一步(请参见图 1)。
在准备要发布的应用时,须配置、构建和测试应用的发布版本。配置任务非常简单,包括基本的代码清理以及有助于优化应用的代码修改任务。此构建流程与调试构建流程相似,可使用 JDK 和 Android SDK 工具完成。测试任务可充当最终检查程序,确保您的应用在真实的条件下按预期运行。当您准备好要发布的应用时,您会有一个已签署的 APK 文件,您可以将其直接分发给用户或通过应用市场(如 Google Play)分发。
本文总结了您在进行应用发布准备时必须执行的主要任务。本文中描述的任务适用于所有 Android 应用,无论它们是通过何种方式发布或分发给用户。如果您通过 Google Play 发布应用,您还应阅读 Google Play 发布检查单,以确保可供发布的应用满足 Google Play 的所有要求。
注:最佳做法是,在执行本文概要列出的任务前,确保您的应用满足针对功能、性能和稳定性的所有发布条件。
图 1. 发布准备是必须执行的一项开发任务,也是发布流程的第一步。
简介
要向用户发布您的应用,您需要创建一个可供发布的软件包,供用户在基于 Android 的设备上安装和运行。可供发布的软件包所包含的组件与调试 APK 文件(编译的源代码、资源、清单文件等)相同,所用的构建工具也相同。然而,与调试 APK 文件不同的是,可供发布的 APK 文件已使用您自己的证书签署,且已通过 zipalign 工具优化。
如果您是使用 Android Studio 构建您的应用,则签署和优化任务通常可以无缝完成。例如,您可以将 Android Studio 与 Gradle 构建文件搭配使用,一次性完成应用的编译、签署和优化任务。从命令行构建时,您也可以通过配置 Gradle 构建文件来实现相同目的。如需了解有关使用 Gradle 构建文件的更多详情,请参阅构建系统指南。
要准备发布应用,您通常需要执行五个主要任务(请参见图 2)。每个主要任务都包含一个或多个小任务,具体取决于您如何发布应用。例如,如果您是通过 Google Play 发布您的应用,在配置要发布的应用时,您可能希望向您的清单中添加特殊过滤规则。同样,为遵循 Google Play 发布指南,在收集要发布的资料时,您可能必须准备屏幕截图,并创建宣传文字。
在全面调试和测试您的应用后,您通常要执行图 2 中列出的任务。Android SDK 包含多个工具,可帮助您测试和调试您的 Android 应用。如需了解详细信息,请参阅开发指南中的调试和测试部分。
图 2. 准备发布应用时您需要执行五个主要任务。
收集资料和资源
要开始进行应用发布准备,您需要收集多个支持项目。这至少要包括用于签署应用的加密密钥和一个应用图标。您还可能需要包括一个最终用户许可协议。
加密密钥
Android 系统要求每个已安装应用均须使用应用开发者所拥有的证书(即开发者持有其私钥的证书)进行数字签署。Android 系统使用此证书作为识别应用作者和确定应用之间信任关系的手段。您用于签署的证书不需要由证书颁发机构签署;Android 系统允许您使用自签署证书签署您的应用。要详细了解证书要求,请参阅签署您的应用。
重要说明:您的应用必须使用一个有效期于 2033 年 10 月 22 日以后结束的加密密钥进行签署。
另外,如果您的应用访问某个服务或使用第三方库,而该服务或库要求您使用基于私钥的密钥,您可能还必须获取其他发布密钥。
应用图标
确保您有一个应用图标,且其符合建议的图标指南之相关要求。您的应用图标应帮助用户在设备的主屏幕和启动器窗口中识别您的应用。它还应显示在“Manage Applications”、“My Downloads”及其他位置。此外,Google Play 等发布服务可将您的图标显示给用户。
注:如果您要在 Google Play 上发布应用,则需要创建一个高分辨率版本的应用图标。如需了解详细信息,请参阅应用的图形资源。
最终用户许可协议
考虑为您的应用准备一个最终用户许可协议 (EULA)。EULA 有助于为个人、组织和知识产权提供保护,我们建议您为您的应用提供一个 EULA。
其他资料
您可能还需要准备宣传资料和营销资料来推广您的应用。例如,如果您要在 Google Play 上发布您的应用,则需要准备一些宣传文字,还需要创建应用的屏幕截图。如需了解详细信息,请参阅应用的图形资源
配置要发布的应用
在收集所有支持材料后,您可以开始配置要发布的应用。本部分总结了我们建议您在发布应用之前对源代码、资源文件和应用清单进行的配置更改。尽管本部分列出的大部分配置更改为可选更改,但却是一些不错的编码实践,我们建议您实施这些更改。在某些情况下,您可能已在开发流程中进行了这些配置更改。
选择一个适当的软件包名称
确保您选择的软件包名称适用于应用的整个生命周期。在向用户分发应用后,您将无法更改软件包名称。您可以在应用的清单文件中设置此软件包名称。如需了解详细信息,请参阅 package 属性文档。
关闭日志记录和调试
在构建要发布的应用之前,务必关闭日志记录并停用调试选项。您可以通过在源文件中移除对 Log
方法的调用来关闭日志记录。您可以通过在清单文件中将 android:debuggable
属性从 <application>
标记中移除,或通过在清单文件中将 android:debuggable
属性设为 false
来停用调试。另外,移除在您的项目中创建的所有日志文件或静态测试文件。
您还应移除您添加到代码中的所有 Debug
跟踪调用,例如 startMethodTracing()
和 stopMethodTracing()
方法调用。
重要说明:如果使用 WebView
显示付费内容或使用 JavaScript 接口,请确保停用针对应用的调试,因为调试将允许用户使用 Chrome DevTools 注入脚本和提取内容。要停用调试,请使用 WebView.setWebContentsDebuggingEnabled()
方法。
清理项目目录
清理项目,并确保其符合 Android 项目中描述的目录结构。将散乱文件或孤立文件留在项目中会妨碍您编译应用,而且可能会导致应用行为不可预测。您至少应执行以下清理任务:
- 查看
jni/
、lib/
和src/
目录的内容。jni/
目录应仅包含与 Android NDK 关联的源文件,例如.c
、.cpp
、.h
和.mk
文件。lib/
目录应仅包含第三方库文件或专有库文件,包括预构建共享库和静态库(例如,.so
文件)。src/
目录应仅包含您的应用的源文件(.java
和.aidl
文件)。src/
目录不应包含任何.jar
文件。 - 检查项目,查看是否有应用不使用的私有或专有数据文件,并移除它们。例如,查看项目的
res/
目录是否有您不再使用的旧的可绘制对象文件、布局文件和值文件,并删除它们。 - 检查
lib/
目录,查看是否有应用不再使用的测试库,并移除它们。 - 查看
assets/
目录的内容以及res/raw/
目录是否有需要在发布前更新或移除的原始资源文件和静态文件。
查看并更新您的清单和 Gradle 构建设置
验证是否已正确设置以下清单和构建文件项目:
- <uses-permission> 元素
您应仅指定那些与应用相关且必需的权限。
android:icon
和android:label
属性这些属性位于 <application> 元素中,您必须为它们指定相应的值。
android:versionCode
和android:versionName
属性。这些属性位于 <manifest> 元素中,我们建议您为它们指定相应的值。如需了解详细信息,请参阅对您的应用进行版本控制。
如果要在 Google Play 上发布应用,您还可以设置一些其他的清单或构建文件元素。例如,android:minSdkVersion
和 android:targetSdkVersion
属性,它们位于 <uses-sdk> 元素中。如需了解有关这些属性及其他 Google Play 设置的详细信息,请参阅 Google Play 上的过滤器。
地址兼容性问题
Android 提供多个工具和技术,让您的应用能够与各种设备兼容。为了使您的应用可供尽可能多的用户使用,请执行以下操作:
- 增加针对多种屏幕配置的支持
确保您遵循支持多种屏幕的最佳做法。通过支持多种屏幕配置,您可以创建一款在 Android 系统支持的任何屏幕尺寸上都可以正常运行且显示良好的应用。
- 针对 Android 平板电脑设备优化您的应用。
如果您的应用是专为早于 Android 3.0 的设备设计的,则可遵循针对 Android 3.0 优化应用中所述的指南和最佳做法使其与 Android 3.0 设备兼容。
- 考虑使用支持库
如果您的应用是专为运行 Android 3.x 的设备设计的,则可通过向应用项目添加支持库让您的应用与较早的 Android 版本兼容。支持库提供您可以添加到 Android 应用的静态支持库,有了它,您可以使用较旧平台版本上不提供的 API,或使用不属于框架 API 的实用程序 API。
更新服务器和服务的网址
如果您的应用访问远程服务器或服务,请确保您使用的是服务器或服务的生产网址或路径,而不是测试网址或路径。
实现许可(如果您在 Google Play 上发布)
如果您要通过 Google Play 发布付费应用,请考虑增加针对 Google Play 许可的支持。许可让您可以根据当前用户是否已购买应用来控制他们对应用的访问。使用 Google Play 许可属于可选项,即使您要通过 Google Play 发布应用也是如此。
如需了解有关 Google Play 许可服务以及如何在应用中使用这种服务的详细信息,请参阅应用许可。
构建要发布的应用
在完成应用的配置后,您可以将其构建到已签署和优化的可供发布的 APK 文件中。JDK 包含用于签署 APK 文件的工具(Keytool 和 Jarsigner);Android SDK 包含用于编译和优化 APK 文件的工具。如果您是使用 Android Studio 或是从命令行使用 Gradle 构建系统,您可以将整个构建流程自动化。如需了解有关配置 Gradle 构建的详细信息,请参阅配置 Gradle 构建。
通过 Android Studio 进行构建
您可以使用与 Android Studio 集成的 Gradle 构建系统来构建已使用您的私钥进行签署并已优化的可供发布的 APK 文件。要了解如何从 Android Studio 设置和运行构建,请参阅在 Android Studio 中构建和运行项目。
此构建流程假设您拥有适合签署应用的证书和私钥。如果您没有适合的证书和私钥,Android Studio 可帮助您生成一个。如需了解有关签署流程的详细信息,请参阅签署您的应用。
准备外部服务器和资源
如果您的应用依靠远程服务器,请确保该服务器安全,且已针对生产使用进行配置。如果您要在应用中实现应用内购买结算并要在远程服务器上执行签名验证步骤,这一点尤为重要。
另外,如果您的应用从远程服务器或实时服务(如内容 Feed)获取内容,请确保您正在提供的内容是最新的,并且可用于生产环境。
测试要发布的应用
测试应用的发布版本可帮助确保应用在真实的设备和网络条件下正常运行。理想情况下,您应至少在一台手机尺寸的设备和一台平板电脑尺寸的设备上测试您的应用,以验证用户界面元素的大小是否正确,且应用的性能和电池效率是否在可接受的范围内。
开始测试时,请先参阅测试什么。本文总结了测试时您应考虑的常见 Android 情况。完成测试并确定应用的发布版本可正确运行后,即可向用户发布您的应用。如需了解详细信息,请参阅向用户发布您的应用。如果您要在 Google Play 上发布应用,请参阅 Google Play 发布检查单。
Android BLE蓝牙配置全流程(二) 附APP源码
事务的难度远远低于对事物的恐惧
0.前言
在 Android BLE蓝牙配置全流程(一) 附APP源码 中已经完成了前期的准备工作,在这里我们得到了需要连接的蓝牙设备的名字和地址,需要完成蓝牙设备的连接和数据传输功能。
1.初始化界面
首先需要完成对这个界面包含的控件进行初始化,代码如下:
private TextView text1, text2;
private EditText editText;
private Button btn_send;
private String status = "disconnected";
private final static String TAG = "hello";
//界面初始化
private void initView() {
text1 = findViewById(R.id.text1);
text2 = findViewById(R.id.text2);
editText = findViewById(R.id.edit_text);
btn_send = findViewById(R.id.btn_send);
text1.setText("状态:" + status);
btn_send.setonClickListener(this);
}
控件text1
中显示目前的连接状态,控件text2
中显示接收到的数据,控件edittext
中输入要发送的数据,控件btn_send
是发送按钮
2.获取前一个界面传来的数据
首先得到前一个界面传来的数据,代码如下:
public static String EXTRAS_DEVICE_NAME = "DEVICE_NAME";
public static String EXTRAS_DEVICE_ADDRESS = "DEVICE_ADDRESS";
private Bundle bundle;
private String name;
private String address;
bundle = getIntent().getExtras();
name = bundle.getString(EXTRAS_DEVICE_NAME);
address = bundle.getString(EXTRAS_DEVICE_ADDRESS);
name
中存放蓝牙设备的名字,address
中存放蓝牙设备的地址。
3.创建一个服务类并继承Service
本文中创建了一个BlutoothBLEService
并继承了Service
,完成蓝牙设备的初始化、连接、断开连接、读取特征值、写入特征值、设置特征值变化通知以及获取已连接蓝牙的所有服务等操作。
public class BlutoothBLEService extends Service {
...............................
}
3.1 蓝牙设备初始化
public final static String ACTION_GATT_CONNECTED = "com.example.bluebledemo.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_disCONNECTED = "com.example.bluebledemo.ACTION_GATT_disCONNECTED";
public final static String ACTION_GATT_SERVICES_disCOVERED = "com.example.bluebledemo.ACTION_GATT_SERVICES_disCOVERED";
public final static String ACTION_DATA_AVAILABLE = "com.example.bluebledemo.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA = "com.example.bluebledemo.EXTRA_DATA";
private final static int STATE_disCONNECTED = 0;
private final static int STATE_CONNECTING = 1;
private final static int STATE_CONNECTED = 2;
private final static String TAG = "hello";
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
//蓝牙初始化 在第二个界面中的ServiceConnection中调用
public boolean initialize() {
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(BLUetoOTH_SERVICE);
if (mBluetoothManager == null) {
Log.i(TAG, "initialize: mBluetoothManager 初始化失败");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.i(TAG, "initialize: mBluetoothAdapter 初始化失败");
return false;
}
return true;
}
上面代码是在第二个界面成功绑定这个服务之后,在回调函数ServiceConnection
中被执行,下文将详细介绍,这里先把这个服务详细的说清楚。
3.2 蓝牙设备的连接
private BluetoothGatt mBluetoothGatt;
//蓝牙连接外围设备
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.i(TAG, "connect: BLE not init");
return false;
}
if (mBluetoothDeviceAddress != null && mBluetoothGatt != null && mBluetoothDeviceAddress.equals(address)) {
Log.i(TAG, "connect: Trying to use an existing mBluetoothGatt for connection");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.i(TAG, "connect: device not found");
return false;
}
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.i(TAG, "connect: Trying to create a connection");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
在第二个界面中会创建一个BlutoothBLEService
类的对象,然后调用这个方法并传入地址,即可连接成功。mBluetoothGatt = device.connectGatt(this, false, mGattCallback)
通过绑定回调函数,本节的后续操作都可在回调函数中完成相应的操作。
3.3 蓝牙设备取消连接
//取消连接
public void disconnect() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.i(TAG, "disconnect: BLE not init");
return;
}
mBluetoothGatt.disconnect();
}
//关闭所有蓝牙连接
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
同样通过BlutoothBLEService
类的对象调用这个方法即可取消连接。
3.4 读取特征值
//读取特征值
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.i(TAG, "readCharacteristic: BLE not init");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
在蓝牙设备连接成功后自动读一次特征值。
3.5 写入特征值
//写入特征值
public void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.i(TAG, "writeCharacteristic: BLE not init");
return;
}
mBluetoothGatt.writeCharacteristic(characteristic);
}
这是完成手机APP向蓝牙设备写数据的操作。
3.6 设置特征值变化通知
这个很重要,如果不设置话,手机APP是接收不到蓝牙设备发送的数据。
//设置特征值变化通知
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristicNotification, boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.i(TAG, "setCharacteristicNotification: BLE not init");
return;
}
mBluetoothGatt.setCharacteristicNotification(characteristicNotification, enabled);
BluetoothGattDescriptor descriptor = characteristicNotification.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (enabled) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
} else {
descriptor.setValue(BluetoothGattDescriptor.disABLE_NOTIFICATION_VALUE);
}
mBluetoothGatt.writeDescriptor(descriptor);
}
这个方法同样也是在第二个界面中调用的,其中UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"
是HC-08
蓝牙设备的监听UUID,如果蓝牙设备不是一个型号,请更改为自己所用的蓝牙设备的监听UUID
,
3.7 获取已连接蓝牙的所有服务
//获取已连接蓝牙的所有服务
public List<BluetoothGattService> getDupportedGattServices() {
if (mBluetoothGatt == null) {
return null;
}
return mBluetoothGatt.getServices();
}
返回已经连接蓝牙设备的所有服务。
3.8 读取蓝牙设备的RSSI值
//读取RSSI
public void readRSSi() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.i(TAG, "readRSSi: BLE not init");
return;
}
mBluetoothGatt.readRemoteRSSi();
}
该方法返回的是已连接的蓝牙设备的信号值(RSSI
)。
3.9 连接外围设备的回调函数
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
//重写 蓝牙连接状态
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (status == BluetoothGatt.GATT_SUCCESS) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
mBluetoothGatt.discoverServices(); //发现服务
} else if (newState == BluetoothProfile.STATE_disCONNECTED) {
intentAction = ACTION_GATT_disCONNECTED;
mConnectionState = STATE_disCONNECTED;
broadcastUpdate(intentAction);
}
}
}
//重写 蓝牙发现服务
@Override
public void onServicesdiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_disCOVERED);
Log.i(TAG, "onServicesdiscovered: 蓝牙发现服务");
} else {
Log.i(TAG, "onServicesdiscovered: 蓝牙发现服务失败" + status);
}
}
//重写 蓝牙读特征
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "onCharacteristicRead: is called");
byte[] sucString = characteristic.getValue();
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
//重写 蓝牙写特征
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.i(TAG, "onCharacteristicWrite: 写数据成功");
}
//重写 蓝牙特征改变
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.i(TAG, "onCharacteristicChanged: changed changed changed changed changed ");
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
//重写 蓝牙读描述值
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "onDescriptorRead: Read Read Read");
byte[] desc = descriptor.getValue();
if (desc == null) {
Log.i(TAG, "onDescriptorRead: desc is null null null");
}
}
}
//重写 蓝牙写描述值
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "onDescriptorWrite: Write Write Write");
}
}
@Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "onReliableWriteCompleted: onReliableWriteCompleted onReliableWriteCompleted onReliableWriteCompleted");
}
}
//重写 获取蓝牙信号值
@Override
public void onReadRemoteRSSi(BluetoothGatt gatt, int RSSi, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "onReliableWriteCompleted: RSSI RSSI RSSI");
broadcastUpdate(ACTION_DATA_AVAILABLE, RSSi);
}
}
};
在这个回调函数中可以得到已连接的蓝牙设备的所有状态,包括:是否连接成功、是否发送数据成功以及是否读取数据成功等等,然后在这个服务中通过broadCast
(广播)将信息发出去,在第二个界面中设置广播的监听接收器,进而完成数据的传输。
3.10 发送广播
//更新广播
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendbroadcast(intent);
}
//更新广播
private void broadcastUpdate(final String action, int RSSi) {
final Intent intent = new Intent(action);
intent.putExtra(EXTRA_DATA, RSSi);
sendbroadcast(intent);
}
//更新广播
private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
final byte[] data = characteristic.getValue();
//将data的数据传输给主空间中保存
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for (byte byteChar : data) {
stringBuilder.append(String.format("%02X", byteChar));
Log.i(TAG, "broadcastUpdate: byteChar is:" + byteChar);
}
intent.putExtra("BLE_BYTE_DATA", data);
intent.putExtra(EXTRA_DATA, new String(data));
}
sendbroadcast(intent);
}
通过广播的形式将数据发出去,在第二个界面中通过设置过滤器接收对应的广播。
3.11 获取服务的对象
当在第二个界面中成功绑定了该服务,通过调用这个方法即可返回一个该服务的对象。
public class LocalBinder extends Binder {
public BlutoothBLEService getService() {
return BlutoothBLEService.this;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public boolean onUnbind(Intent intent) {
close();
return super.onUnbind(intent);
}
4.绑定服务
在完成服务的代码编写之后,接着需要在第二个界面中绑定该服务,在onCreat
方法中进行绑定,代码如下:
Intent getServiceIntent = new Intent(this, BlutoothBLEService.class);
bindService(getServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
5.绑定服务的回调函数
是否成功的绑定了服务,我们可以在回调函数中获知,代码如下:
private BlutoothBLEService mBlutoothBLEService;
//服务(BlutoothBLEService.class)连接 回调函数
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBlutoothBLEService = ((BlutoothBLEService.LocalBinder) service).getService();
if (!mBlutoothBLEService.initialize()) {
Log.i(TAG, "onServiceConnected: MainActivity BLE not init");
finish();
}
Log.i(TAG, "onServiceConnected: 8888888888888");
mBlutoothBLEService.connect(address);
}
@Override
public void onServicedisconnected(ComponentName name) {
mBlutoothBLEService = null;
}
};
如果绑定成功,首先执行mBlutoothBLEService = ((BlutoothBLEService.LocalBinder) service).getService()
获取这个服务的对象,然后执行mBlutoothBLEService.initialize()
对服务进行初始化。
6.注册/取消注册广播
//注册广播和IntentFilter
@Override
protected void onResume() {
registerReceiver(mbroadCastReceiver, makeGattUpdateIntentFilter());
if (mBlutoothBLEService != null) {
Log.i(TAG, "onResume: 99999999999999");
boolean res = mBlutoothBLEService.connect(address);
Log.i(TAG, "onResume: " + res);
}
super.onResume();
}
//取消注册广播和IntentFilter
@Override
protected void onDestroy() {
unregisterReceiver(mbroadCastReceiver);
mBlutoothBLEService = null;
super.onDestroy();
}
上述代码利用registerReceiver(mbroadCastReceiver, makeGattUpdateIntentFilter())
和unregisterReceiver(mbroadCastReceiver)
方法完成注册和取消注册广播,下面的代码是设置广播接收和过滤器。
//IntentFilter 设置过滤 与广播进行注册
public IntentFilter makeGattUpdateIntentFilter() {
IntentFilter filter = new IntentFilter();
filter.addAction(BlutoothBLEService.ACTION_GATT_CONNECTED);
filter.addAction(BlutoothBLEService.ACTION_GATT_disCONNECTED);
filter.addAction(BlutoothBLEService.ACTION_GATT_SERVICES_disCOVERED);
filter.addAction(BlutoothBLEService.ACTION_DATA_AVAILABLE);
return filter;
}
//设置广播接收 服务(BlutoothBLEService.class)传过来得信息
broadcastReceiver mbroadCastReceiver = new broadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String actionString = intent.getAction();
if (BlutoothBLEService.ACTION_GATT_CONNECTED.equals(actionString)) { //蓝牙设备连接成功
Log.i(TAG, "onReceive: " + name + " 连接成功");
status = "Connected";
updateConnectionState(status);
} else if (BlutoothBLEService.ACTION_GATT_disCONNECTED.equals(actionString)) { //蓝牙设备连接失败
Log.i(TAG, "onReceive: " + name + " 断开连接");
status = "disconnected";
updateConnectionState(status);
} else if (BlutoothBLEService.ACTION_GATT_SERVICES_disCOVERED.equals(actionString)) { //蓝牙设备设置服务
Log.i(TAG, "onReceive: 广播接收到服务");
displayGattServices(mBlutoothBLEService.getDupportedGattServices());
} else if (BlutoothBLEService.ACTION_DATA_AVAILABLE.equals(actionString)) { //蓝牙设备有数据提供
Log.i(TAG, "onReceive: 有数据");
displayData(intent.getExtras().getString(BlutoothBLEService.EXTRA_DATA), intent);
}
}
};
其中displayGattServices(mBlutoothBLEService.getDupportedGattServices())
进一步完成发现服务,displayData(intent.getExtras().getString(BlutoothBLEService.EXTRA_DATA), intent)
进一步完成数据接收的处理,updateConnectionState(status)
是完成蓝牙状态的更新操作,这三个代码将在下文进行说明。
7.发现已连接蓝牙设备服务
public static String CHaraCTERISTIC_UUID = "0000ffe1-0000-1000-8000-00805f9b34fb";
private BluetoothGattCharacteristic target_chara;
private Handler mHandler = new Handler();
//服务
public void displayGattServices(List<BluetoothGattService> gattServices) {
if (gattServices == null) {
return;
}
for (BluetoothGattService service : gattServices) {
List<BluetoothGattCharacteristic> gattcharacteristics = service.getcharacteristics();
for (final BluetoothGattCharacteristic characteristic : gattcharacteristics) {
if (characteristic.getUuid().toString().equals(CHaraCTERISTIC_UUID)) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mBlutoothBLEService.readCharacteristic(characteristic);
}
}, 200);
mBlutoothBLEService.setCharacteristicNotification(characteristic, true);
target_chara = characteristic;
}
}
}
}
其中CHaraCTERISTIC_UUID
时HC-08
蓝牙设备的读写特征UUID
,如果使用的蓝牙设备型号不一样请改为自己所用的蓝牙设备的读写特征UUID
,根据这个UUID
找到蓝牙设备的读写特征,从而设置特征变化通知和记录这个特征。
8.数据的接收
//接收的数据
public void displayData(String rev_string, Intent intent) {
try {
byte[] data = intent.getByteArrayExtra("BLE_BYTE_DATA");
if (data == null) {
Log.i(TAG, "displayData: data is empty");
return;
}
rev_string = new String(data, 0, data.length, "GB2312");
} catch (UnsupportedEncodingException e) {
e.printstacktrace();
}
rev_str = rev_str + rev_string;
runOnUiThread(new Runnable() {
@Override
public void run() {
text2.setText(rev_str);
}
});
}
上面的代码完成数据的接收,并将接收到的数据放入控件text2
中进行追加显示。
9.更新蓝牙连接状态
private tempHandler myHandler = new tempHandler();
private String rev_str = "";
//更新蓝牙连接状态
private void updateConnectionState(String Status) {
Message msg = new Message();
msg.what = 1;
Bundle bundle = new Bundle();
bundle.putString("connect_state", status);
msg.setData(bundle);
myHandler.sendMessage(msg);
}
private class tempHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1:
String str;
str = msg.getData().getString("connect_state");
text1.setText("状态:" + str);
break;
}
super.handleMessage(msg);
}
}
在控件text1
中更新显示当前蓝牙的连接状态。
10.发送数据
//发送数据线程
public class sendDataThread implements Runnable {
public sendDataThread() {
new Thread(this).start();
}
@Override
public void run() {
if (editText.getText() != null) {
byte[] buff = null;
try {
buff = editText.getText().toString().getBytes("GB2312");
Log.i(TAG, "run: " + buff.length);
} catch (UnsupportedEncodingException e) {
e.printstacktrace();
}
int[] sendDataLens = dataSparate(buff.length);
for (int i = 0; i < sendDataLens[0]; i++) {
byte[] data20 = new byte[20];
for (int j = 0; j < 20; j++) {
data20[j] = buff[i * 20 + j];
}
target_chara.setValue(data20);
mBlutoothBLEService.writeCharacteristic(target_chara);
}
if (sendDataLens[1] != 0) {
byte[] lastData = new byte[sendDataLens[1]];
for (int i = 0; i < sendDataLens[1]; i++) {
lastData[i] = buff[sendDataLens[0] * 20 + i];
}
if (lastData != null) {
target_chara.setValue(lastData);
mBlutoothBLEService.writeCharacteristic(target_chara);
} else {
Log.i(TAG, "run: last是空的");
}
}
}
}
}
//发送数据实现点击事件
@Override
public void onClick(View v) {
new sendDataThread();
}
//数据分包处理
private int[] dataSparate(int len) {
int[] lens = new int[2];
lens[0] = len / 20;
lens[1] = len % 20;
return lens;
}
通过点击发送按钮,创建一个发送数据的线程,发送的数据将进行分包处理依次发送,确保即使数据太大也能成功的发送。
至此Android的BLE蓝牙(HC-08)的配置全流程都已详细的说明,如有任何问题可以留言或者私信我,看到会及时回复,上述的内容如果有误,还请各位大佬不吝赐教,在此,祝愿各位学习顺利,前途无限~
Android BLE蓝牙(HC-08)源码需要可以前往下载 Android BLE蓝牙 HC-08 APP开发源码(实测可用)
android FrameWork学习(二)Android系统源码调试
android studio调试,有图
今天关于[android] 天气app布局练习和二的介绍到此结束,谢谢您的阅读,有关Android 8.1 源码_启动篇(二) -- 深入研究 zygote(转 Android 9.0 分析)、Android APP发布(二)发布前准备、Android BLE蓝牙配置全流程(二) 附APP源码、android FrameWork学习(二)Android系统源码调试等更多相关知识的信息可以在本站进行查询。
本文标签: