Android system — 链接器命名空间(linker namespace)源码分析

2024-04-29 12:36

本文主要是介绍Android system — 链接器命名空间(linker namespace)源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android system — 链接器命名空间(linker namespace)源码分析

  • 1. 源码分析
  • 2. do_dlopen
  • 3. find_library_internal
    • 3.1 find_loaded_library_by_soname
    • 3.2 load_library
    • 3.3 find_library_in_linked_namespace

1. 源码分析

  在前一篇文章Android system — Android链接器命名空间(Android 11后)中我们介绍了Android linker namespace的概念、作用及相关配置属性,这一篇我们会跟踪Anroid linker namespace的源码实现。

  应用程序对应的可执行文件app_process会创建一个类加载器classloader并调用System.loadLibrary加载so库,最终会调用至android_dlopen_ext;而native的二进制bin文件是通过dlopen获取so库的handle,但是无论是dlopen还是android_dlopen_ext最后都是调用do_dlopen函数,所以从do_dlopen开始分析。

2. do_dlopen

源码位置:bionic/linker/linker.cpp

  • 调用find_containing_library根据solist获取dlopen调用地址对应的so文件的soinfo指针。
  • 调用get_caller_namespace获取调用者对应的命名空间,调用者so文件对应的soinfo的primary_namespace_成员就是其对应的命名空间。
  • 调用find_library寻找待加载so文件的soinfo指针,如果找到了就获取其对应的handle信息,否则就返回失败。这里还可以得到一个信息,android早期版本中dlopen加载so文件返回的handle就是so文件对应的soinfo结构体指针,现在获取的handle并不是简单的soinfo结构体指针
//bionic/linker/linker.cpp
void* do_dlopen(const char* name, int flags,const android_dlextinfo* extinfo,const void* caller_addr) {std::string trace_prefix = std::string("dlopen: ") + (name == nullptr ? "(nullptr)" : name);ScopedTrace trace(trace_prefix.c_str());ScopedTrace loading_trace((trace_prefix + " - loading and linking").c_str());//获取caller_addr调用地址对应的so文件的soinfo指针soinfo* const caller = find_containing_library(caller_addr);//获取caller调用者的命名空间android_namespace_t* ns = get_caller_namespace(caller);LD_LOG(kLogDlopen,"dlopen(name=\"%s\", flags=0x%x, extinfo=%s, caller=\"%s\", caller_ns=%s@%p, targetSdkVersion=%i) ...",name,flags,android_dlextinfo_to_string(extinfo).c_str(),caller == nullptr ? "(null)" : caller->get_realpath(),ns == nullptr ? "(null)" : ns->get_name(),ns,get_application_target_sdk_version());......ProtectedDataGuard guard;//得到此so文件对应的soinfo指针soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);loading_trace.End();if (si != nullptr) {//通过soinfo指针得到对应的handlevoid* handle = si->to_handle();LD_LOG(kLogDlopen,"... dlopen calling constructors: realpath=\"%s\", soname=\"%s\", handle=%p",si->get_realpath(), si->get_soname(), handle);si->call_constructors();failure_guard.Disable();LD_LOG(kLogDlopen,"... dlopen successful: realpath=\"%s\", soname=\"%s\", handle=%p",si->get_realpath(), si->get_soname(), handle);return handle;}return nullptr;
}

3. find_library_internal

刚刚do_dlopen中,最后我们发现最后会调用find_library寻找待加载so文件的soinfo指针,而根据代码来看,后续实际调用为find_library-->find_libraries-->find_library_internal

//bionic/linker/linker.cpp
static soinfo* find_library(android_namespace_t* ns,const char* name, int rtld_flags,const android_dlextinfo* extinfo,soinfo* needed_by) {soinfo* si = nullptr;if (name == nullptr) {si = solist_get_somain();} else if (!find_libraries(ns,needed_by,&name,1,&si,nullptr,0,rtld_flags,extinfo,false /* add_as_children */)/*实际调用find_libraries */) {if (si != nullptr) {soinfo_unload(si);}return nullptr;}si->increment_ref_count();return si;
}
// add_as_children - add first-level loaded libraries (i.e. library_names[], but
// not their transitive dependencies) as children of the start_with library.
// This is false when find_libraries is called for dlopen(), when newly loaded
// libraries must form a disjoint tree.
bool find_libraries(android_namespace_t* ns,soinfo* start_with,const char* const library_names[],size_t library_names_count,soinfo* soinfos[],std::vector<soinfo*>* ld_preloads,size_t ld_preloads_count,int rtld_flags,const android_dlextinfo* extinfo,bool add_as_children,std::vector<android_namespace_t*>* namespaces) {
......// Step 1: expand the list of load_tasks to include// all DT_NEEDED libraries (do not load them just yet)for (size_t i = 0; i<load_tasks.size(); ++i) {LoadTask* task = load_tasks[i];soinfo* needed_by = task->get_needed_by();bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children);task->set_extinfo(is_dt_needed ? nullptr : extinfo);task->set_dt_needed(is_dt_needed);// Note: start from the namespace that is stored in the LoadTask. This namespace// is different from the current namespace when the LoadTask is for a transitive// dependency and the lib that created the LoadTask is not found in the// current namespace but in one of the linked namespaces.android_namespace_t* start_ns = const_cast<android_namespace_t*>(task->get_start_from());LD_LOG(kLogDlopen, "find_library_internal(ns=%s@%p): task=%s, is_dt_needed=%d",start_ns->get_name(), start_ns, task->get_name(), is_dt_needed);if (!find_library_internal(start_ns, task, &zip_archive_cache, &load_tasks, rtld_flags)/* 调用find_library_internal,从namespace find library*/) {return false;}
......return true;
}

因此我们直接分析find_library_internal函数。

  • 调用find_loaded_library_by_soname()在caller命名空间和链接的命名空间中查找库是否已经加载。
  • 如果上一步未找到就调用load_library()尝试在caller命名空间中加载库。
  • 如果前两步都没成功就尝试调用find_library_in_linked_namespace()在caller命名空间的所有链接命名空间中搜索库,并尝试调用load_library在链接命名空间中加载库。
static bool find_library_internal(android_namespace_t* ns,LoadTask* task,ZipArchiveCache* zip_archive_cache,LoadTaskList* load_tasks,int rtld_flags) {soinfo* candidate;//在caller命名空间和链接的命名空间中查找库是否已经加载if (find_loaded_library_by_soname(ns, task->get_name(), true /* search_linked_namespaces */,&candidate)) {LD_LOG(kLogDlopen,"find_library_internal(ns=%s, task=%s): Already loaded (by soname): %s",ns->get_name(), task->get_name(), candidate->get_realpath());task->set_soinfo(candidate);return true;}// Library might still be loaded, the accurate detection// of this fact is done by load_library.TRACE("[ \"%s\" find_loaded_library_by_soname failed (*candidate=%s@%p). Trying harder... ]",task->get_name(), candidate == nullptr ? "n/a" : candidate->get_realpath(), candidate);//如果上一步未找到就调用load_library尝试在caller命名空间中加载库if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags,true /* search_linked_namespaces */)) {return true;}// workaround 方案:Android Nougat 7.0以后不会生效// TODO(dimitry): workaround for http://b/26394120 (the exempt-list)if (ns->is_exempt_list_enabled() && is_exempt_lib(ns, task->get_name(), task->get_needed_by())) {// For the libs in the exempt-list, switch to the default namespace and then// try the load again from there. The library could be loaded from the// default namespace or from another namespace (e.g. runtime) that is linked// from the default namespace.LD_LOG(kLogDlopen,"find_library_internal(ns=%s, task=%s): Exempt system library - trying namespace %s",ns->get_name(), task->get_name(), g_default_namespace.get_name());ns = &g_default_namespace;if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags,true /* search_linked_namespaces */)) {return true;}}// END OF WORKAROUND//如果前两步都没成功就尝试在caller命名空间的所有链接命名空间中搜索库并尝试在链接命名空间中加载// if a library was not found - look into linked namespaces// preserve current dlerror in the case it fails.DlErrorRestorer dlerror_restorer;LD_LOG(kLogDlopen, "find_library_internal(ns=%s, task=%s): Trying %zu linked namespaces",ns->get_name(), task->get_name(), ns->linked_namespaces().size());for (auto& linked_namespace : ns->linked_namespaces()) {if (find_library_in_linked_namespace(linked_namespace, task)) {// Library is already loaded.if (task->get_soinfo() != nullptr) {// n.b. This code path runs when find_library_in_linked_namespace found an already-loaded// library by soname. That should only be possible with a exempt-list lookup, where we// switch the namespace, because otherwise, find_library_in_linked_namespace is duplicating// the soname scan done in this function's first call to find_loaded_library_by_soname.return true;}if (load_library(linked_namespace.linked_namespace(), task, zip_archive_cache, load_tasks,rtld_flags, false /* search_linked_namespaces */)) {LD_LOG(kLogDlopen, "find_library_internal(ns=%s, task=%s): Found in linked namespace %s",ns->get_name(), task->get_name(), linked_namespace.linked_namespace()->get_name());return true;}}}return false;
}

3.1 find_loaded_library_by_soname

  • 首先会判断加载的库名称(也就是dlopen传入的so名称)是否为绝对路径,如果是直接返回false。
  • 如果不是绝对路径就调用find_loaded_library_by_soname()在caller命名空间已经加载的so库列表中找是否有待加载的so库。
  • 如果上一步没找到就遍历caller命名空间所有链接的命名空间,并调用 android_namespace_link_t::is_accessible()进行权限判断。权限判断失败直接返回false,权限判断成功就调用find_loaded_library_by_soname在链接命名空间已经加载的so库列表中找是否有待加载的so库。
// Returns true if library was found and false otherwise
static bool find_loaded_library_by_soname(android_namespace_t* ns,const char* name,bool search_linked_namespaces,soinfo** candidate) {*candidate = nullptr;// Ignore filename with path.//如果是绝对路径就直接返回falseif (strchr(name, '/') != nullptr) {return false;}//在caller命名空间已经加载的so库列表中找是否有待加载的so库 bool found = find_loaded_library_by_soname(ns, name, candidate);//如果没找到就遍历caller命名空间所有链接命名空间 if (!found && search_linked_namespaces) {// if a library was not found - look into linked namespacesfor (auto& link : ns->linked_namespaces()) {//首先判断权限if (!link.is_accessible(name)) {continue;}android_namespace_t* linked_ns = link.linked_namespace();//权限判断通过就在链接命名空间中已经加载的so库列表中找是否有待加载的so库if (find_loaded_library_by_soname(linked_ns, name, candidate)) {return true;}}}return found;
}

android_namespace_link_t::is_accessible()进行权限判断。

  • 先判断此链接的allow_all_shared_libs_ 是否为true,如果为true说明当前命名空间中无法加载的so库都可以在链接命名空间中搜索加载。
  • 如果allow_all_shared_libs_为false就需要进一步判断待加载的so库是否在此链接命名空间的shared_lib_sonames_列表中。
// bionic/linker/linker_namespaces.h
bool is_accessible(const char* soname) const {return allow_all_shared_libs_ || shared_lib_sonames_.find(soname) != shared_lib_sonames_.end();
}

3.2 load_library

load_library先判断extinfo是否为空,只有java层调用System.loadLibrary时extinfo才不为空。对于在native层中直接调用dlopen和android_dlopen_ext而言extinfo都为空。

  • extinfo不为空,直接调用重载的load_library
  • extinfo为空,需要先调用open_library将so文件从磁盘加载到内存中,在调用重载的load_library
static bool load_library(android_namespace_t* ns,LoadTask* task,ZipArchiveCache* zip_archive_cache,LoadTaskList* load_tasks,int rtld_flags,bool search_linked_namespaces) {const char* name = task->get_name();soinfo* needed_by = task->get_needed_by();const android_dlextinfo* extinfo = task->get_extinfo();//判断extinfo是否为空,只有从java层调用System.loadLibrary来到者才extinfo才不为空if (extinfo != nullptr && (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) != 0) {off64_t file_offset = 0;if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) {file_offset = extinfo->library_fd_offset;}std::string realpath;if (!realpath_fd(extinfo->library_fd, &realpath)) {if (!is_first_stage_init()) {PRINT("warning: unable to get realpath for the library \"%s\" by extinfo->library_fd. ""Will use given name.",name);}realpath = name;}task->set_fd(extinfo->library_fd, false);task->set_file_offset(file_offset);return load_library(ns, task, load_tasks, rtld_flags, realpath, search_linked_namespaces);}LD_LOG(kLogDlopen,"load_library(ns=%s, task=%s, flags=0x%x, search_linked_namespaces=%d): calling ""open_library",ns->get_name(), name, rtld_flags, search_linked_namespaces);// Open the file.off64_t file_offset;std::string realpath;//对于直接native调用dlopen而言先调用open_library把so文件载入内存。int fd = open_library(ns, zip_archive_cache, name, needed_by, &file_offset, &realpath);if (fd == -1) {if (task->is_dt_needed()) {if (needed_by->is_main_executable()) {DL_OPEN_ERR("library \"%s\" not found: needed by main executable", name);} else {DL_OPEN_ERR("library \"%s\" not found: needed by %s in namespace %s", name,needed_by->get_realpath(), task->get_start_from()->get_name());}} else {DL_OPEN_ERR("library \"%s\" not found", name);}return false;}task->set_fd(fd, true);task->set_file_offset(file_offset);//调用重载的load_library加载so文件获取soinfo结构return load_library(ns, task, load_tasks, rtld_flags, realpath, search_linked_namespaces);
}

open_library会尝试从磁盘中加载so文件

  • 判断是否为绝对路径,是的话直接打开。
  • 尝试从LD_LIBRARY_PATH设置的路径中打开文件(ld_library_paths)
  • 尝试从DT_RUNPATH打开
  • 尝试从default_library_paths中加载
static int open_library(android_namespace_t* ns,ZipArchiveCache* zip_archive_cache,const char* name, soinfo *needed_by,off64_t* file_offset, std::string* realpath) {TRACE("[ opening %s from namespace %s ]", name, ns->get_name());// If the name contains a slash, we should attempt to open it directly and not search the paths.//如果是绝对路径直接打开文件if (strchr(name, '/') != nullptr) {return open_library_at_path(zip_archive_cache, name, file_offset, realpath);}// LD_LIBRARY_PATH has the highest priority. We don't have to check accessibility when searching// the namespace's path lists, because anything found on a namespace path list should always be// accessible.//尝试从LD_LIBRARY_PATH设置的路径中打开(ld_library_paths)int fd = open_library_on_paths(zip_archive_cache, name, file_offset, ns->get_ld_library_paths(), realpath);// Try the DT_RUNPATH, and verify that the library is accessible.//尝试从DT_RUNPATH打开,从代码可以看到LD_LIBRARY_PATH并没有校验is_accessible,但是DT_RUNPATH会校验,因此向通过编译设置DT_RUNPATH和直接export LD_LIBRARY_PATH环境变量效果并不一样if (fd == -1 && needed_by != nullptr) {fd = open_library_on_paths(zip_archive_cache, name, file_offset, needed_by->get_dt_runpath(), realpath);if (fd != -1 && !ns->is_accessible(*realpath)) {close(fd);fd = -1;}}// Finally search the namespace's main search path list.//尝试从default_library_paths中加载if (fd == -1) {fd = open_library_on_paths(zip_archive_cache, name, file_offset, ns->get_default_library_paths(), realpath);}return fd;
}

open_library如果成功加载文件后就调用重载的load_library。

  • 调用is_accessible进行权限检查,检查通过直接调用soinfo_alloc为so文件申请soinfo结构体。
  • is_accessible权限检查未通过调用is_greylisted判断是否在灰名单中,如果在就调用soinfo_alloc为so文件申请soinfo结构体。
  • is_accessible权限检查未通过,也不在灰名单中则此caller命名空间无权限加载此so文件,打印错误日志。(我的测试bin就是在这里报错的)
static bool load_library(android_namespace_t* ns,LoadTask* task,LoadTaskList* load_tasks,int rtld_flags,const std::string& realpath,bool search_linked_namespaces) {off64_t file_offset = task->get_file_offset();const char* name = task->get_name();const android_dlextinfo* extinfo = task->get_extinfo();LD_LOG(kLogDlopen,"load_library(ns=%s, task=%s, flags=0x%x, realpath=%s, search_linked_namespaces=%d)",ns->get_name(), name, rtld_flags, realpath.c_str(), search_linked_namespaces);......//文件不在临时文件系统,is_accessible权限检查没有通过// do not check accessibility using realpath if fd is located on tmpfs// this enables use of memfd_create() for appsif ((fs_stat.f_type != TMPFS_MAGIC) && (!ns->is_accessible(realpath))) {// TODO(dimitry): workaround for http://b/26394120 - the exempt-listconst soinfo* needed_by = task->is_dt_needed() ? task->get_needed_by() : nullptr;//是否在exempt-list中if (is_exempt_lib(ns, name, needed_by)) {// print warning only if needed by non-system libraryif (needed_by == nullptr || !is_system_library(needed_by->get_realpath())) {const soinfo* needed_or_dlopened_by = task->get_needed_by();const char* sopath = needed_or_dlopened_by == nullptr ? "(unknown)" :needed_or_dlopened_by->get_realpath();DL_WARN_documented_change(24,"private-api-enforced-for-api-level-24","library \"%s\" (\"%s\") needed or dlopened by \"%s\" ""is not accessible by namespace \"%s\"",name, realpath.c_str(), sopath, ns->get_name());add_dlwarning(sopath, "unauthorized access to",  name);}} else {//如果权限检查未通过,也不在exempt-list中则证明当前caller命名空间无权限加载此库文件。(app报错就是在此报错的)// do not load libraries if they are not accessible for the specified namespace.const char* needed_or_dlopened_by = task->get_needed_by() == nullptr ?"(unknown)" :task->get_needed_by()->get_realpath();DL_OPEN_ERR("library \"%s\" needed or dlopened by \"%s\" is not accessible for the namespace \"%s\"",name, needed_or_dlopened_by, ns->get_name());// do not print this if a library is in the list of shared libraries for linked namespacesif (!maybe_accessible_via_namespace_links(ns, name)) {PRINT("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the"" namespace: [name=\"%s\", ld_library_paths=\"%s\", default_library_paths=\"%s\","" permitted_paths=\"%s\"]",name, realpath.c_str(),needed_or_dlopened_by,ns->get_name(),android::base::Join(ns->get_ld_library_paths(), ':').c_str(),android::base::Join(ns->get_default_library_paths(), ':').c_str(),android::base::Join(ns->get_permitted_paths(), ':').c_str());}return false;}}//如果上述检查都通过就为加载的so文件申请一个soinfo结构soinfo* si = soinfo_alloc(ns, realpath.c_str(), &file_stat, file_offset, rtld_flags);task->set_soinfo(si);// Read the ELF header and some of the segments.if (!task->read(realpath.c_str(), file_stat.st_size)) {task->remove_cached_elf_reader();task->set_soinfo(nullptr);soinfo_free(si);return false;}......return true;
}

is_accessible进行权限检查,依次进行如下检查,有一项检查通过则直接返回true。

  • 判断命名空间的is_isolated_是否为true,如果不是则证明不是严格隔离,权限检查通过。(这就是通常说的如果命名空间不是严格隔离的则可以加载任意绝对路径的库文件的原理)
  • 判断是否在白名单中
  • 判断是否在ld_library_paths中(LD_LIBRARY_PATH设置)
  • 判断是否在default_library_paths中
  • 判断是否在特权路径permitted_paths中
// bionic/linker/linker_namespaces.cpp
// Given an absolute path, can this library be loaded into this namespace?
bool android_namespace_t::is_accessible(const std::string& file) {//判断命名空间的is_isolated_,即是否为严格隔离,如果不是则权限检查通过。if (!is_isolated_) {return true;}//判断是否在允许名单中if (!allowed_libs_.empty()) {const char *lib_name = basename(file.c_str());if (std::find(allowed_libs_.begin(), allowed_libs_.end(), lib_name) == allowed_libs_.end()) {return false;}}//判断是否在ld_library_paths中(LD_LIBRARY_PATH设置)for (const auto& dir : ld_library_paths_) {if (file_is_in_dir(file, dir)) {return true;}}//判断是否在default_library_paths中for (const auto& dir : default_library_paths_) {if (file_is_in_dir(file, dir)) {return true;}}//判断是否在特权路径permitted_paths中for (const auto& dir : permitted_paths_) {if (file_is_under_dir(file, dir)) {return true;}}return false;
}

3.3 find_library_in_linked_namespace

如果第一步和第二步都失败了就遍历caller命名空间的链接命名空间并调用find_library_in_linked_namespace。

  • 调用find_loaded_library_by_soname在链接命名空间中已经加载的so库列表中查找是否存在待加载so文件,如果是绝对路径直接返回false。(这次传入false所以不会调用链接命名空间的链接命名空间,这也是链接命名空间不具有传递性的原理)
  • 无论找没找到都调用is_accessible权限检查(检查 链接命名空间的allow_all_shared_libs_是否为True或者so文件是否在链接命名空间的共享库列表shared_lib_sonames_中。)
static bool find_library_in_linked_namespace(const android_namespace_link_t& namespace_link,LoadTask* task) {android_namespace_t* ns = namespace_link.linked_namespace();soinfo* candidate;bool loaded = false;std::string soname;//检查so库在链接命名空间中是否加载,传入false不会去检查链接命名空间的链接命名空间(这也是链接命名空间不具有传递性的原理)if (find_loaded_library_by_soname(ns, task->get_name(), false, &candidate)) {loaded = true;soname = candidate->get_soname();} else {soname = resolve_soname(task->get_name());}//进行权限检查if (!namespace_link.is_accessible(soname.c_str())) {// the library is not accessible via namespace_linkLD_LOG(kLogDlopen,"find_library_in_linked_namespace(ns=%s, task=%s): Not accessible (soname=%s)",ns->get_name(), task->get_name(), soname.c_str());return false;}// if library is already loaded - return it//如果已经加载就返回if (loaded) {LD_LOG(kLogDlopen, "find_library_in_linked_namespace(ns=%s, task=%s): Already loaded",ns->get_name(), task->get_name());task->set_soinfo(candidate);return true;}// returning true with empty soinfo means that the library is okay to be// loaded in the namespace but has not yet been loaded there before.LD_LOG(kLogDlopen, "find_library_in_linked_namespace(ns=%s, task=%s): Ok to load", ns->get_name(),task->get_name());task->set_soinfo(nullptr);return true;
}

这篇关于Android system — 链接器命名空间(linker namespace)源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/946102

相关文章

深入解析Wireshark2:过滤器与TCP三次握手分析

目录 1 wireshark过滤器 1.1 捕获过滤器 设置目的 工作原理 注意事项 设置步骤 1.2 显示过滤器 设置目的 工作原理 注意事项 设置步骤 过滤关系 复合过滤表达式 2 常见显示过滤需求及其表达式 2.1 数据链路层 2.2 网络层 2.3 传输层 2.4 应用层 2.5 其他常见 3 使用wireshark分析TCP三次握手 4 使用wi

HNU-算法设计与分析-作业2

第二次作业【分治算法】 文章目录 第二次作业【分治算法】<1>算法实现题 2-2 马的Hamilton周游路线问题<2> 算法实现题 2-3 半数集问题<3>算法实现题 2-6 排列的字典序问题<4> 算法实现题 2-7 集合划分问题 <1>算法实现题 2-2 马的Hamilton周游路线问题 ▲题目重述 8*8的国际象棋棋盘上的一只马,恰好走过除起点外的其他63个位

MAC上搭建eclipse+android开发环境

mac jdk,eclipse,adt 下载eclipse 找到“Eclipse IDE for java Developers”,选择“Mac OS X(Cocoa)” 根据你的mac处理器,去选在是下载64bit或32bit, 这里我的Pro是i5处理器支持64位,点击右边的“Mac OS X 64bit”。(如何查看MAC处理器多少位:关于本机->

Mac 下配置Android NDK 环境变量[附带网盘NDK下载]

转自:http://blog.csdn.net/yy1300326388/article/details/46288907 下载NDK | 点击下载 | 密码: d6zp 解压NDK | 详情转移到这里 //ndk$为NDK所在的目录ndk$ chmod a+x android-ndk-r10c-darwin-x86_64.binndk$ ./android-n

【MATLAB源码-第208期】基于matlab的改进A*算法和传统A*算法对比仿真;改进点:1.无斜穿障碍物顶点2.删除中间多余节点,减少转折。

操作环境: MATLAB 2022a 1、算法描述 改进A*算法的优点分析 改进A*算法相对于传统A*算法在多个方面进行了优化,包括避免斜穿障碍物顶点、删除中间多余节点以及提高搜索效率。这些改进措施使得路径规划更加高效、安全和可靠,特别是在复杂环境中表现尤为突出。本文将详细讨论这些改进及其带来的优点。 1. 避免斜穿障碍物顶点,避免碰撞 在路径规划过程中,斜穿障碍物顶点会带来很大的风险

android系统数据库

http://blog.csdn.net/u014600432/article/details/41010929 短信数据库: /data/data/com.android.providers.telephony/databases/mmssms.db 设置数据库 /data/data/com.android.providers.settings/databases/settings.

贷款借钱平台 贷款源码 小额贷款系统 卡卡贷源码 小额贷款源码 贷款平台

贷款平台源码/卡卡贷源码/小贷源码/完美版 , 数据库替换application/database.php 源码下载:https://download.csdn.net/download/m0_66047725/89268533 更多资源下载:关注我。

2.28学习内容,android,dialog自定义宽高、ios判断网络可用性

android设定dialog宽高读: http://blog.csdn.net/true100/article/details/43982763 mDialog = new Dialog(getActivity(), R.style.IsDelDialog);//自定义的样式,没有贴出代码来 mDialog.setContentView(view); Window dialogWindow

2.20 android连接wifi,解决mWifiManager.addNetwork(info)=-1

来自:http://blog.csdn.net/sasoritattoo/article/details/8285926 先转一段代码: package com.widget.hotspot;import android.os.Bundle;import android.app.Activity;import android.content.BroadcastReceiver;imp

android 记录和恢复ListView滚动的位置 四种方法

方法1 setOnScorll Listener 方法,记录滑动位置 listView.setOnScrollListener(new OnScrollListener() { /** * 滚动状态改变时调用 */ @Override public void onScrollStateChanged(AbsListView view, int scrollState