HarmonyOS的应用必须用js来桥接native。需要使用ace_napi仓中提供的napi接口来处理js交互。napi提供的接口名与三方Node.js一致,目前支持部分接口,符号表见ace_napi仓中的libnapi.ndk.json文件。

开发流程

在DevEco Studio的模板工程中包含使用Native API的默认工程,使用File->New->Create Project创建Native C++模板工程。创建后在main目录下会包含cpp目录,可以使用ace_napi仓下提供的napi接口进行开发。

js侧通过import引入native侧包含处理js逻辑的so,如:import hello from 'libhello.so',意为使用libhello.so的能力,native侧通过napi接口创建的js对象会给到应用js侧的hello对象。

开发建议

注册建议

● nm_register_func对应的函数需要加上static,防止与其他so里的符号冲突。

● 模块注册的入口,即使用__attribute__((constructor))修饰的函数的函数名需要确保不与其他模块重复。

so命名规则

so命名必须符合以下规则:

● 每个模块对应一个so。

● 如模块名为hello,则so的名字为libhello.so,napi_module中nm_modname字段应为hello,大小写与模块名保持一致,应用使用时写作:import hello from 'libhello.so'。

js对象线程限制

ark引擎会对js对象线程使用进行保护,使用不当会引起应用crash,因此需要遵循如下原则:

● napi接口只能在js线程使用。

● env与线程绑定,不能跨线程使用。native侧js对象只能在创建时的线程使用,即与线程所持有的env绑定。

头文件引入限制

在使用napi的对象和方法时需要引用"napi/native_api.h"。否则在只引入三方库头文件时,会出现接口无法找到的编译报错。

napi_create_async_work接口说明

napi_create_async_work里有两个回调:

● execute:用于异步处理业务逻辑。因为不在js线程中,所以不允许调用napi的接口。业务逻辑的返回值可以返回到complete回调中处理。

● complete:可以调用napi的接口,将execute中的返回值封装成js对象返回。此回调在js线程中执行。

napi_status napi_create_async_work(napi_env env,
napi_value async_resource,
napi_value async_resource_name,
napi_async_execute_callback execute,
napi_async_complete_callback complete,
void* data,
napi_async_work* result)

  

storage 模块——同步异步接口封装

模块简介

本示例通过实现 storage 模块展示了同步和异步方法的封装。storage 模块实现了数据的保存、获取、删除、清除功能。

接口声明

import { AsyncCallback } from './basic';
declare namespace storage {
function get(key: string, callback: AsyncCallback<string>): void;
function get(key: string, defaultValue: string, callback: AsyncCallback<string>): void;
function get(key: string, defaultValue?: string): Promise<string>;
function set(key: string, value: string, callback: AsyncCallback<string>): void;
function remove(key: string, callback: AsyncCallback<void>): void;
function clear(callback: AsyncCallback<void>): void;
function getSync(key: string, defaultValue?: string): string;
function setSync(key: string, value: string): void;
function removeSync(key: string): void;
function clearClear(): void;
}
export default storage;

  

具体实现

完整代码参见仓下路径:OpenHarmony/arkui_napi仓库sample/native_module_storage/

1、模块注册

如下,注册了4个同步接口(getSync、setSync、removeSync、clearSync)、4个异步接口(get、set、remove、clear)。

/***********************************************
* Module export and register
***********************************************/
static napi_value StorageExport(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("get", JSStorageGet),
DECLARE_NAPI_FUNCTION("set", JSStorageSet),
DECLARE_NAPI_FUNCTION("remove", JSStorageDelete),
DECLARE_NAPI_FUNCTION("clear", JSStorageClear), DECLARE_NAPI_FUNCTION("getSync", JSStorageGetSync),
DECLARE_NAPI_FUNCTION("setSync", JSStorageSetSync),
DECLARE_NAPI_FUNCTION("deleteSync", JSStorageDeleteSync),
DECLARE_NAPI_FUNCTION("clearSync", JSStorageClearSync),
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
} // storage module
static napi_module storage_module = {.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = StorageExport,
.nm_modname = "storage",
.nm_priv = ((void*)0),
.reserved = {0}}; // storage module register
extern "C" __attribute__((constructor)) void StorageRegister()
{
napi_module_register(&storage_module);
}

  

2、getSync 函数实现

如上注册时所写,getSync 对应的函数是 JSStorageGetSync 。从 gKeyValueStorage 中获取数据后,创建一个字符串对象并返回。

static napi_value JSStorageGetSync(napi_env env, napi_callback_info info)
{
GET_PARAMS(env, info, 2);
NAPI_ASSERT(env, argc >= 1, "requires 1 parameter");
char key[32] = {0};
size_t keyLen = 0;
char value[128] = {0};
size_t valueLen = 0; // 参数解析
for (size_t i = 0; i < argc; i++) {
napi_valuetype valueType;
napi_typeof(env, argv[i], &valueType); if (i == 0 && valueType == napi_string) {
napi_get_value_string_utf8(env, argv[i], key, 31, &keyLen);
} else if (i == 1 && valueType == napi_string) {
napi_get_value_string_utf8(env, argv[i], value, 127, &valueLen);
break;
} else {
NAPI_ASSERT(env, false, "type mismatch");
}
} // 获取数据的业务逻辑,这里简单地从一个全局变量中获取
auto itr = gKeyValueStorage.find(key);
napi_value result = nullptr;
if (itr != gKeyValueStorage.end()) {
// 获取到数据后创建一个string类型的JS对象
napi_create_string_utf8(env, itr->second.c_str(), itr->second.length(), &result);
} else if (valueLen > 0) {
// 没有获取到数据使用默认值创建JS对象
napi_create_string_utf8(env, value, valueLen, &result);
} else {
NAPI_ASSERT(env, false, "key does not exist");
}
// 返回结果
return result;
}

  

3、get 函数实现

如上注册时所写,get对应的函数式JSStorageGet。

static napi_value JSStorageGet(napi_env env, napi_callback_info info)
{
GET_PARAMS(env, info, 3);
NAPI_ASSERT(env, argc >= 1, "requires 1 parameter"); // StorageAsyncContext是自己定义的一个类,用于保存执行过程中的数据
StorageAsyncContext* asyncContext = new StorageAsyncContext(); asyncContext->env = env; // 获取参数
for (size_t i = 0; i < argc; i++) {
napi_valuetype valueType;
napi_typeof(env, argv[i], &valueType); if (i == 0 && valueType == napi_string) {
napi_get_value_string_utf8(env, argv[i], asyncContext->key, 31, &asyncContext->keyLen);
} else if (i == 1 && valueType == napi_string) {
napi_get_value_string_utf8(env, argv[i], asyncContext->value, 127, &asyncContext->valueLen);
} else if (i == 1 && valueType == napi_function) {
napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef);
break;
} else if (i == 2 && valueType == napi_function) {
napi_create_reference(env, argv[i], 1, &asyncContext->callbackRef);
} else {
NAPI_ASSERT(env, false, "type mismatch");
}
} napi_value result = nullptr; // 根据参数判断开发者使用的是promise还是callback
if (asyncContext->callbackRef == nullptr) {
// 创建promise
napi_create_promise(env, &asyncContext->deferred, &result);
} else {
napi_get_undefined(env, &result);
} napi_value resource = nullptr;
napi_create_string_utf8(env, "JSStorageGet", NAPI_AUTO_LENGTH, &resource); napi_create_async_work(
env, nullptr, resource,
// 回调1:此回调由napi异步执行,里面就是需要异步执行的业务逻辑。由于是异步线程执行,所以不要在此通过napi接口操作JS对象。
[](napi_env env, void* data) {
StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
auto itr = gKeyValueStorage.find(asyncContext->key);
if (itr != gKeyValueStorage.end()) {
strncpy_s(asyncContext->value, 127, itr->second.c_str(), itr->second.length());
asyncContext->status = 0;
} else {
asyncContext->status = 1;
}
},
// 回调2:此回调在上述异步回调执行完后执行,此时回到了JS线程来回调开发者传入的回调
[](napi_env env, napi_status status, void* data) {
StorageAsyncContext* asyncContext = (StorageAsyncContext*)data;
napi_value result[2] = {0};
if (!asyncContext->status) {
napi_get_undefined(env, &result[0]);
napi_create_string_utf8(env, asyncContext->value, strlen(asyncContext->value), &result[1]);
} else {
napi_value message = nullptr;
napi_create_string_utf8(env, "key does not exist", NAPI_AUTO_LENGTH, &message);
napi_create_error(env, nullptr, message, &result[0]);
napi_get_undefined(env, &result[1]);
}
if (asyncContext->deferred) {
// 如果走的是promise,那么判断回调1的结果
if (!asyncContext->status) {
// 回调1执行成功(status为1)时触发,也就是触发promise里then里面的回调
napi_resolve_deferred(env, asyncContext->deferred, result[1]);
} else {
// 回调1执行失败(status为0)时触发,也就是触发promise里catch里面的回调
napi_reject_deferred(env, asyncContext->deferred, result[0]);
}
} else {
// 如果走的是callback,则通过napi_call_function调用callback回调返回结果
napi_value callback = nullptr;
napi_value returnVal;
napi_get_reference_value(env, asyncContext->callbackRef, &callback);
napi_call_function(env, nullptr, callback, 2, result, &returnVal);
napi_delete_reference(env, asyncContext->callbackRef);
}
napi_delete_async_work(env, asyncContext->work);
delete asyncContext;
},
(void*)asyncContext, &asyncContext->work);
napi_queue_async_work(env, asyncContext->work); return result;
}

  

4、js示例代码

import storage from 'libstorage.so';

export default {
testGetSync() {
const name = storage.getSync('name');
console.log('name is ' + name);
},
testGet() {
storage.get('name')
.then(date => {
console.log('name is ' + data);
})
.catch(error => {
console.log('error: ' + error);
});
}
}

  

NetServer 模块——native与js对象绑定

模块简介

本示例展示了on/off/once订阅方法的实现,同时也包含了 C++ 与 js对象通过 wrap 接口的绑定。NetServer 模块实现了一个网络服务。

接口声明

export class NetServer {
function start(port: number): void;
function stop(): void;
function on('start' | 'stop', callback: Function): void;
function once('start' | 'stop', callback: Function): void;
function off('start' | 'stop', callback: Function): void;
}

  

具体实现

完整代码参见:OpenHarmony/arkui_napi仓库sample/native_module_netserver/

1、模块注册

static napi_value NetServer::Export(napi_env env, napi_value exports)
{
const char className[] = "NetServer";
napi_property_descriptor properties[] = {
DECLARE_NAPI_FUNCTION("start", JS_Start),
DECLARE_NAPI_FUNCTION("stop", JS_Stop),
DECLARE_NAPI_FUNCTION("on", JS_On),
DECLARE_NAPI_FUNCTION("once", JS_Once),
DECLARE_NAPI_FUNCTION("off", JS_Off),
};
napi_value netServerClass = nullptr; napi_define_class(env, className, sizeof(className), JS_Constructor, nullptr, countof(properties), properties,
&netServerClass); napi_set_named_property(env, exports, "NetServer", netServerClass); return exports;
}

  

2、在构造函数中绑定 C++ 与 JS 对象

napi_value NetServer::JS_Constructor(napi_env env, napi_callback_info cbinfo)
{
napi_value thisVar = nullptr;
void* data = nullptr;
napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisVar, &data); // C++ Native对象,准备与JS对象映射在一起
NetServer* netServer = new NetServer(env, thisVar); // 使用napi_wrap将netServer与thisVar(即当前创建的这个JS对象)做绑定
napi_wrap(
env, thisVar, netServer,
// JS对象由引擎自动回收释放,当JS对象释放时触发该回调,在改回调中释放netServer
[](napi_env env, void* data, void* hint) {
printf("NetServer::Destructor\n");
NetServer* netServer = (NetServer*)data;
delete netServer;
},
nullptr, nullptr); return thisVar;
}

  

3、从 JS 对象中取出 C++ 对象

napi_value NetServer::JS_Start(napi_env env, napi_callback_info cbinfo)
{
size_t argc = 1;
napi_value argv[1] = {0};
napi_value thisVar = nullptr;
void* data = nullptr;
napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data); NetServer* netServer = nullptr;
// 通过napi_unwrap从thisVar中取出C++对象
napi_unwrap(env, thisVar, (void**)&netServer); NAPI_ASSERT(env, argc >= 1, "requires 1 parameter"); napi_valuetype valueType;
napi_typeof(env, argv[0], &valueType);
NAPI_ASSERT(env, valueType == napi_number, "type mismatch for parameter 1"); int32_t port = 0;
napi_get_value_int32(env, argv[0], &port); // 开启服务
netServer->Start(port); napi_value result = nullptr;
napi_get_undefined(env, &result);
return result;
}

  

netServer->Start后回调通过on注册的start事件。

int NetServer::Start(int port)
{
printf("NetServer::Start thread_id: %ld \n", uv_thread_self()); struct sockaddr_in addr;
int r; uv_ip4_addr("0.0.0.0", port, &addr); r = uv_tcp_init(loop_, &tcpServer_);
if (r) {
fprintf(stderr, "Socket creation error\n");
return 1;
} r = uv_tcp_bind(&tcpServer_, (const struct sockaddr*)&addr, 0);
if (r) {
fprintf(stderr, "Bind error\n");
return 1;
} r = uv_listen((uv_stream_t*)&tcpServer_, SOMAXCONN, OnConnection);
if (r) {
fprintf(stderr, "Listen error %s\n", uv_err_name(r));
return 1;
} // 服务启动后触发“start”事件
Emit("start", nullptr); return 0;
}

  

4、注册或释放(on/off/once)事件,以 on 为例

napi_value NetServer::JS_On(napi_env env, napi_callback_info cbinfo)
{
size_t argc = 2;
napi_value argv[2] = {0};
napi_value thisVar = 0;
void* data = nullptr;
napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, &data); NetServer* netServer = nullptr;
// 通过napi_unwrap取出NetServer指针
napi_unwrap(env, thisVar, (void**)&netServer); NAPI_ASSERT(env, argc >= 2, "requires 2 parameter"); // 参数类型校验
napi_valuetype eventValueType;
napi_typeof(env, argv[0], &eventValueType);
NAPI_ASSERT(env, eventValueType == napi_string, "type mismatch for parameter 1"); napi_valuetype eventHandleType;
napi_typeof(env, argv[1], &eventHandleType);
NAPI_ASSERT(env, eventHandleType == napi_function, "type mismatch for parameter 2"); char type[64] = {0};
size_t typeLen = 0; napi_get_value_string_utf8(env, argv[0], type, 63, &typeLen); // 注册事件handler
netServer->On((const char*)type, argv[1]); napi_value result = nullptr;
napi_get_undefined(env, &result);
return result;
}

  

5、js示例代码

import { NetServer } from 'libnetserver.so';

export default {
testNetServer() {
var netServer = new NetServer();
netServer.on('start', (event) => {});
netServer.start(1000); // 端口号1000, start完成后回调上面注册的 “start” 回调
}
}

  

在非JS线程中回调JS接口

模块简介

本示例介绍如何在非JS线程中回调JS应用的回调函数。例如JS应用中注册了某个sensor的监听,这个sensor的数据是由一个SA服务来上报的,当SA通过IPC调到客户端时,此时的执行线程是一个IPC通信线程,与应用的JS线程是两个不同的线程。这时就需要将执行JS回调的任务抛到JS线程中才能执行,否则会出现崩溃。

具体实现

完整代码参见:OpenHarmony/arkui_napi仓库sample/native_module_callback/

1、模块注册

如下,注册了1个接口test,会传入一个参数,类型为包含一个参数的函数。

/***********************************************
* Module export and register
***********************************************/
static napi_value CallbackExport(napi_env env, napi_value exports)
{
static napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("test", JSTest)
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
} // callback module define
static napi_module callbackModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = CallbackExport,
.nm_modname = "callback",
.nm_priv = ((void*)0),
.reserved = { 0 },
}; // callback module register
extern "C" __attribute__((constructor)) void CallbackTestRegister()
{
napi_module_register(&callbackModule);
}

  

2、获取env中的loop,抛任务回JS线程

#include <thread>

#include "napi/native_api.h"
#include "napi/native_node_api.h" #include "uv.h" struct CallbackContext {
napi_env env = nullptr;
napi_ref callbackRef = nullptr;
int retData = 0;
}; void callbackTest(CallbackContext* context)
{
uv_loop_s* loop = nullptr;
// 此处的env需要在注册JS回调时保存下来。从env中获取对应的JS线程的loop。
napi_get_uv_event_loop(context->env, &loop); // 创建uv_work_t用于传递私有数据,注意回调完成后需要释放内存,此处省略生成回传数据的逻辑,传回int类型1。
uv_work_t* work = new uv_work_t;
context->retData = 1;
work->data = (void*)context; // 调用libuv接口抛JS任务到loop中执行。
uv_queue_work(
loop,
work,
// 此回调在另一个普通线程中执行,用于处理异步任务,回调执行完后执行下面的回调。本场景下该回调不需要执行任务。
[](uv_work_t* work) {},
// 此回调会在env对应的JS线程中执行。
[](uv_work_t* work, int status) {
CallbackContext* context = (CallbackContext*)work->data;
napi_handle_scope scope = nullptr;
// 打开handle scope用于管理napi_value的生命周期,否则会内存泄露。
napi_open_handle_scope(context->env, &scope);
if (scope == nullptr) {
return;
} // 调用napi。
napi_value callback = nullptr;
napi_get_reference_value(context->env, context->callbackRef, &callback);
napi_value retArg;
napi_create_int32(context->env, context->retData, &retArg);
napi_value ret;
napi_call_function(context->env, nullptr, callback, 1, &retArg, &ret);
napi_delete_reference(context->env, context->callbackRef); // 关闭handle scope释放napi_value。
napi_close_handle_scope(context->env, scope); // 释放work指针。
if (work != nullptr) {
delete work;
} delete context;
}
);
} static napi_value JSTest(napi_env env, napi_callback_info info)
{
size_t argc = 1;
napi_value argv[1] = { 0 };
napi_value thisVar = nullptr;
void* data = nullptr;
napi_get_cb_info(env, info, &argc, argv, &thisVar, &data); // 获取第一个入参,即需要后续触发的回调函数
napi_valuetype valueType = napi_undefined;
napi_typeof(env, argv[0], &valueType);
if (valueType != napi_function) {
return nullptr;
}
// 存下env与回调函数,用于传递
auto asyncContext = new CallbackContext();
asyncContext->env = env;
napi_create_reference(env, argv[0], 1, &asyncContext->callbackRef);
// 模拟抛到非js线程执行逻辑
std::thread testThread(callbackTest, asyncContext);
testThread.detach(); return nullptr;
}

  

3、  js示例代码

import callback from 'libcallback.so';

export default {
testcallback() {
callback.test((data) => {
console.error('test result = ' + data)
})
}
}

  

Native API在HarmonyOS应用工程中的使用指导的更多相关文章

  1. Native Application 开发详解(直接在程序中调用 ntdll.dll 中的 Native API,有内存小、速度快、安全、API丰富等8大优点)

    文章目录:                   1. 引子: 2. Native Application Demo 展示: 3. Native Application 简介: 4. Native Ap ...

  2. Unity在Android和iOS中如何调用Native API

    本文主要是对unity中如何在Android和iOS中调用Native API进行介绍. 首先unity支持在C#中调用C++ dll,这样可以在Android和iOS中提供C++接口在unity中调 ...

  3. 把 Reative Native 47 版本集成到已有的 Native iOS 工程中

    一.搭建开发环境 http://reactnative.cn/docs/0.46/getting-started.html#content 二.创建一个模板 运行以下命令,创建一个最新版本的 reac ...

  4. react native 之 在现有的iOS工程中集成react native

    在现有的iOS工程中集成react native, 或者说将react native引入到iOS 项目,是RN和iOS混合开发的必经之路 参考官网教程:https://reactnative.cn/d ...

  5. 调用其他VBA工程中的过程和函数以及API函数

    Excel VBA中,同一个应用程序下面包括多个工作簿,每个工作簿都有自己独立的VBAProject 在同一个VBA工程中,使用Call即可调用其他模块中的过程和函数,例如: Call Module2 ...

  6. 【Unity与Android】02-在Unity导出的Android工程中接入Google Admob广告

    我在上一篇文章 [Unity与Android]01-Unity与Android交互通信的简易实现) 中介绍了Unity与Android通讯的基本方法. 这一篇开始进入应用阶段,这次要介绍的是如何在An ...

  7. unity导出工程导入到iOS原生工程中详细步骤

    一直想抽空整理一下unity原生工程导入iOS原生工程中的详细步骤.做iOS+vuforia+unity开发这么长时间了.从最初的小小白到现在的小白.中间趟过了好多的坑.也有一些的小小收货.做一个喜欢 ...

  8. iOS开发--Swift 如何完成工程中Swift和OC的混编桥接(Cocoapods同样适用)

    由于SDK现在大部分都是OC版本, 所以假如你是一名主要以Swift语言进行开发的开发者, 就要面临如何让OC和Swift兼容在一个工程中, 如果你没有进行过这样的操作, 会感觉异常的茫然, 不用担心 ...

  9. step6----->往工程中添加spring boot项目------->修改pom.xml使得我的project是基于spring boot的,而非直接基于spring framework

    文章内容概述: spring项目组其实有多个projects,如spring IO platform用于管理external dependencies的版本,通过定义BOM(bill of mater ...

  10. node-webkit教程(9)native api 之Tray(托盘)

    node-webkit教程(9)native api 之Tray(托盘) 文/玄魂 目录 node-webkit教程(9)native api 之Tray(托盘) 前言 9.1  Tray简介 9.2 ...

随机推荐

  1. 【Azure 事件中心】开启 Apache Flink 制造者 Producer 示例代码中的日志输出 (连接 Azure Event Hub Kafka 终结点)

    问题描述 Azure Event Hub 在标准版以上就默认启用的Kafka终结点,所以可以通过Apache Kafka协议连接到Event Hub进行消息的生产和消费.通过示例代码下载到本地运行后, ...

  2. 浅入Kubernetes(4):使用Minikube体验

    Minikube 打开 https://github.com/kubernetes/minikube/releases/tag/v1.19.0 下载最新版本的二进制软件包(deb.rpm包),再使用 ...

  3. Java Servlet单元测试

    Java Servlet单元测试 1. 解决痛点 虽然目前主流的开发方式,很多都是通过controll或者微服务提供api.但是不免还是需要写几个servlet完成接口开发.按照常规,servlet调 ...

  4. 7、zookeeper应用场景-分布式锁

    分布式锁 实现原理:有序节点+watch监听机制实现 分布式锁有多种实现方式,比如通过数据库.redis都可实现.作为分布式协同工具Zookeeper,当然也有着标准的实现方式.下面介绍在zookee ...

  5. ASP.NET Core 移除已注册的过滤器

    背景 ABP vNext 默认对异常响应进行了处理,现在某个项目需要自定义异常响应结果. 问题 在 ABP vNext 的 MVC 模块当中,可以看到是通过 AddService(typeof(Abp ...

  6. 快速带你入门css

    css复习笔记 1. css样式值 1.1 文字样式 1 p{ 2 font-size: 30px;/*设置文字大小*/ 3 font-weight: bold;/*文字加粗*/ 4 font-sty ...

  7. 从零开始写 Docker(五)---基于 overlayfs 实现写操作隔离

    本文为从零开始写 Docker 系列第五篇,在 pivotRoot 基础上通过 overlayfs 实现写操作隔离,达到容器中写操作和宿主机互不影响. 完整代码见:https://github.com ...

  8. vite启动dev的项目,在nginx做代理的时候,二级目录尾要加/

    vite启动dev的项目,在nginx做代理的时候,二级目录尾要加/ vite dev开发启动的时候, url最后不加/,系统不能使用,所以代理的时候,没加/,代理跳转过去,就回导致页面加载不出来,j ...

  9. 在使用sudo apt-get -f install的时候,出现了更换介质的问题-依赖问题

    这四个选项都选上,然后apt-get update 在修补依赖问题,apt-get -f install      就好了

  10. 谈谈Android中的消息提示那些坑

    Android中的消息提示无非就那几种,弹个窗(Toast或SnackBar),或者是弹出个对话框(Dialog),最近在使用的时候也是遇到了问题,有时候导致APP闪退 稍微研究会,总结了一下使用过程 ...