From 7cc83fd26a51ad97c835ebaf5e5d632bd244d4fc Mon Sep 17 00:00:00 2001 From: chenhantao Date: Tue, 10 Jun 2025 11:27:19 +0800 Subject: [PATCH] Issue:https://gitee.com/openharmony/docs/issues/ICDXDT Signed-off-by: chenhantao Change-Id: I3c6f1cc102918cbd35e227f914dc6473ec08c3fe --- zh-cn/application-dev/napi/Readme-CN.md | 6 +- .../figures/zh_cn_image_20-25-06-40-15-09.png | Bin 0 -> 17735 bytes .../napi/napi-faq-about-common-basic.md | 313 +++++++++++++ .../napi/napi-faq-about-memory-leak.md | 218 +++++++++ .../napi/napi-faq-about-stability.md | 129 ++++++ zh-cn/application-dev/napi/use-napi-faqs.md | 422 ++---------------- zh-cn/application-dev/website.md | 6 +- 7 files changed, 700 insertions(+), 394 deletions(-) create mode 100644 zh-cn/application-dev/napi/figures/zh_cn_image_20-25-06-40-15-09.png create mode 100644 zh-cn/application-dev/napi/napi-faq-about-common-basic.md create mode 100644 zh-cn/application-dev/napi/napi-faq-about-memory-leak.md create mode 100644 zh-cn/application-dev/napi/napi-faq-about-stability.md diff --git a/zh-cn/application-dev/napi/Readme-CN.md b/zh-cn/application-dev/napi/Readme-CN.md index 2b9e11c4919..0786a9faf16 100644 --- a/zh-cn/application-dev/napi/Readme-CN.md +++ b/zh-cn/application-dev/napi/Readme-CN.md @@ -51,7 +51,11 @@ - [使用Node-API接口创建、切换和销毁上下文环境](use-napi-about-context.md) - [使用Node-API接口产生的异常日志/崩溃分析](use-napi-about-crash.md) - [使用Node-API调用返回值为promise的ArkTS方法](use-napi-method-promise.md) - - [Node-API常见问题](use-napi-faqs.md) + - Node-API常见问题汇总 + - [Node-API常见问题](use-napi-faqs.md) + - [稳定性相关问题汇总](napi-faq-about-stability.md) + - [内存泄漏相关问题汇总](napi-faq-about-memory-leak.md) + - [常见基本功能问题汇总](napi-faq-about-common-basic.md) - 使用JSVM-API实现JS与C/C++语言交互 - [JSVM-API简介](jsvm-introduction.md) - [JSVM-API支持的数据类型和接口](jsvm-data-types-interfaces.md) diff --git a/zh-cn/application-dev/napi/figures/zh_cn_image_20-25-06-40-15-09.png b/zh-cn/application-dev/napi/figures/zh_cn_image_20-25-06-40-15-09.png new file mode 100644 index 0000000000000000000000000000000000000000..22c49abbad4943edf7adc69f5e48c38a9f0df581 GIT binary patch literal 17735 zcmcJ%WmweR7B>vTAPgWil(fVElERSENC^W7N+~5uN_R6fA|WY53y4TcNVkNPG}2un z-TnN*<9+UPz3<28nGei$?b)&R+H0@%Td@gMQ<1-oLy3cihIU&~;h{Pj8t4le8ag^Q z2xyr_Swy3u(W5ColzQTdzTtH{l3Lz<`aGf$+t0I6@pal#(awGHmRq29UjlJGYG$nD z7a{(Qk6y5p^gezm9Gq;{(a6yYLwsX$)pv8EgoqSn-!H2X*|E0Tac16SeI#!cHgC@s z%^ux#H+T4rNAjKfIMk>bso`e_&Q1sI`^u_E`|X4DLc_yPZR)l(^bSPzhWVb=IIWKW zpXKG{o+QB;*M;y%iOct@Hj|Zh7pDg*ISw1+<>BGsdApT>paecZU$a= zK|W$oJKJs{C^Bg9Uh0g)CFhcaKG~&}?E5<&3@T3&w9&6|-tZ?77xK9jg`->J{9QUu zHr;ul4cJe(QUX^&VWBki$)UL9?BB5#Wp=hZ^R1<(Kcu0vL4Rpd(*awgsI2TRb&+#D zcipNxN{8I@prg0_J1M_+;%s*!U#7X=MBU|R%DU=>;X*FmYvnqp4d)6hZRGWyZ%A*x zt2XiTr?!EC!Lw%$)TVI6_W419f$+@9@Ig{qL+uHl&)40YqURjcIJOjjxD0IjlU|*> z6Tr>MDhFVNu!#zvr4*&BUsGKTwbeUeN(qrMgSDj#k^Ed?t-K4>O`~ zy`1W|Y^E3o$Ax!V)}w}*A3+7B=C8oiYM6g^F(8-j44hRMId@@UVf|k3gBA!y*Zmq#qURxTbB6&?o1r(-`vcQ(BY#LmK0Q)<4tJ@95Co9d6cljVcRjt0RWKIel5dKbAf5@&;|wMQO5d z8U>w6k^R!jQA~Z=V^}uO&FFdyg*cbJ(BASp8BC()x?sXH(1D2tf2UPw^Ei|*W+3%8 z<_^9;T!pk&s%mMKlh1#1^ewMNd4Jmn?$PqdB08l;g3`U_uWO*okb09y^F*dk1bO=i9LtN z6ydKQ%KUWbc@RU{LanYbTmdUgE#CvdI+C_DiE-<=55J_=VMqw zqY;yr3+d)ZTdK->#s{AYv46|`Sa0Nh^t)Fw-+JoPT~ivJ3`UKv;_z^shpDuFm$oR-wI6<;EhfDO%)sp{&R6i@R5(B zYHn#=ZBPCXJG8!VN#Qv{u~Hn#uR(8aaWOGGOhTPQQw`RJnp_7zDWYB%#`5`)oD8B*stf*NZ?-eW`o6i@FKIgkL5|!H<<$0IBSEu{S zKduV*#^klc&mOS*oF{y7CVX$tl#&M={XGv1tXGC)O)gRDvx%V+2%b7+?mwbKfD8>Bs82Z(X5$4GnmgTqOD6I<_;rdq-tM4CA;L z2%NeBm6OkRy?I7^11$VPMlK4+$2&r53wMT2CDTccXk?1$H6DnI&`aQVp#M?j=ZrjW zV5>)G5?7PY`cbp>SNo#}bzxV_N7J2nAn(-Pd|B%b&*SCpZsHg#u~MFX!at&%6)w4P zytAOBq!bnwR#dzD=zNAnyKZO4VM}ktr!;T$dw+xd*UR0Z9`Z;7j~x%X+Wk@T>4w@k zXSA0yo*ljEvM1YC;xl1Zp?KrNO(H#~-*kU)D8s?vo9ldsQO#o^T(JJAZp-VaW=2n2 zJERE0d&nEybH{Q!5eMY=qNHG@_`xjkA6l5?(NOflLB#;iZ!A+m9P zlDd(lhQldxd;|g^=5+kbwVkj+#vRyFRxl?A20HM2TOeAU@$tZ1PO8XpzH7Vfb7vzg z?Ds&A_h$6B!N9INSU~xZL!Sk1+*}APL}EA8mA}pp_Q|Ay(E=F$CQx$DCyo9&v1oKm zst}C8`QyY!s;JxU&o9R9k#r6AOE@bD56}^6o0qbV01IL<(2qQ@c4k=UDSH*q7o6Ij zP@ElZ)Oeja@A7aW`nHaW^lA?0g6ZndHfwBZcjWBP0*lcQY9ZEsF~I02)W}^kP)u)u zaiA`e}tqaEYAL*+u=UZ=&HgefgyE z5jNqEbWw&se=V^_*7@7-Gk!n=Nv2O?4*|43Na@9*^RxwUN5~8dHlso(9a-jmU1KaE zBN6C(bF$(Aq6i)4g*0jC{+XAV-?1RxqZ_pV3zU40Fgz9H=tnuVr<$0yNw4(FRTgA? zD26v3tdFv_chYs5%;CsDNea7Wd4LU(K1_d&YW&q~q$8>ZE$+$4Pm%DMUi5yHCgD>* zu`ylklsCA>!O5BOz$GI5e@_Y7Yzf*7k`$qP)7ql{2)ld5nUOf2;~Ghl)&p31XQ7>4 zHYN^7U_QQ)`9{c6iF{$(`zqQOhe85 zb`LojFaTbNV!!z5IKAI=E9-SjwH#jWA&lQjp$(BhxB1mVFLF;(cn_dHDY_| zdFc>mJ2a#+p^)AD!(~Q_+nSibZT-Bz|8gM!))Bzbr@&0;3w!_jwdUqX#`St`q4=nD z;+KK+d{TgB;NH`{bKwhPH7;7_hMD;(cYwm5fPe|kdu*3>I!3^zwrEI4Le?MYkT;9p zLZNZ#jNn8JFz*PMii!$QZVvC-=jZ3!+T{8=B=wsJi}|JStz9eCq`}21YRxLL3Q%c! zV?=RfWo2C*4bA%D;o99ho44+wAqc(l7Jj)BQXruF*Kjs(_!lCl-NmM8D(a3k zz5&V*v<_aVdThvi3v|cQud1qwLS>`3r>B+9SM2^a3fK0~J8$tO?XzIqfSNsQ<-bA2dzhrg*6!^47rj#DR%F}aM8H5sNh zU*B)?)mV24Y|zO|^z&4bfgqoB^-xj+Sq@7|Oo3IpY)#vIUjSSa^KjCiJ?x-RxYV>(dfFpk!1LMasf2T!o1A5K)h`f+){F;W{qblYI5=$ z-oy^r0n1(2r8q5KN)a=dAU4w=`277{x%^y67G6=m)PfHFsCy~r5w(Rqv4!9#-@JPP z5GOSgdttf~7u$WKslHSU@>e$0UX@>08qwo8`KEZw#jv=351^3U=fAhEE>ls3l{M^n z(Q%WG9Mp+upuj<}G%=iJoYNN;GP{gp?x!k#ve;#C$!qS%?Q_zV=DDj##;Dj`SdP+- z%}x~j4zjYx)d8pc5Fobj^KRgt;fSJ&I`snG_ohVTkJFC0 zhfVHAHafzQ-{)!{&~GtR-exVPCmp7+8WB;5HeWbe~S zx)Vt3g`#{||M=FCd1csLj_lg#OLt;J@24Xzbhlh@ZTH*?PpICMkuI2it}|EVXL9@K z^UiF>B%jxE4*n>+<+%Cy4;Fh}jU8QUAu&|ikVas?W8GTmf=f$Urs-pX+TZtBUKBtb zYo;E!Y-$T$0=K?;sxa=3vxt}&!q4|IEj1ONJ$H{ZjY?JV^wGKMD}UJRV+uXW*_E=# z9N%2NPBO<>g80QLtsG2tW|k~0qPCcBeO&tXA?S|Ru{92jq(WLAaM=2hZ8$4 z6K=O|Bso}W0PWDC8Gg0(^uue!l{8gxUUD%V&rjweHa3pu30avzN>9Sl)E`cTn9_Dd z;QdT)uT@(&+0R+akf2e5)^0cB1{2T;ylMjWhfgPd-i*urt?C`1amgEwWi_ClJ7EUD z9Yr2Hl{0-}Cc>me5Dx49q)7N&oG%3v5}^J zHGV^$eQ%#R)a3KavEKKq6n0q|+mezT_u1i2~G&cAyp}?w0Hhb@v^pbbzR`p|L?& zr{arPI#Mucv!H`ucbpfh-2qyjpY8<7@G(4n((_1RJ#^KSHaq-V?6Ol!T8sW;=m>f3 ziABRqWH!Xv#YG3pbH86cc*%&1>RWxHFRzEG zzLr}(hhR(DN6rvcMxP(FC$t!P+2`+(315F!!jaV(OK5$96^&KXX5cz5!gPph@#CXU zdeu+hNO_B2XY^`NGsLfYACC^&?T06GZL^uYB?p1oQg5PmaFTF=g z!=FlC zji*1}->4o{@nuqD-+NPuKpIBXq6EXG<kA3Zi^r7pkv$Vflm$M&*Z|Lc4 zYaYqzd@1dROSSpmRdir82W^FGeo4AD7epWR1;*MwBZ=c#974O>2VObcoT9RaMnqrq z$q*!>W;23m8Q3x4NE!7fOYrO#az*Lf%o#|x}Ji8BqGW><#p0(`C z+@6yj0bu{jaG`N_ZU8ege_RX=j{g9oxQfDCxGX;3@jwe>WKhFI*n+SKJ8+Ppd|1@1 z?4ZB_Fyk)KX9O*Bz|yZog)jv7Iz9;i7#_F!d_Sx{a2!gLkOP2fn6ix{;1vKckpPH` zbrpJEl zm4GiJnN8SH4ioY^-A}0BXC43>oDL+Yq>1Y>Q3e#<0+#tNrSzjHLko+}bM{9X8XC8{ z=yjL?rs0T4HCh2+_goGUqnBRX)Pc3ifnM-oD@!Um|2L-`+Y|1Gt8k;v$bQBmd%f$v%})EC+MU8l*B1g zlwM_PV>GHV78(|U&M^;!Olhb}0?tILaJOYqsz7R%=^;3hyTuu0hFrU{FG zgbBH9PUc0xVi z@MSA*uzk$k=qXz}lK(wTV86v4Seb-R6lmQ0c@`N;pn&+Q<(gwQ0dAL?8rVJu66Dp+ zL!@yh?jV6!lpU+hiRq?G9|U9#IMHfs*IkJ3|M(QQc69qVJK%9ox<2dwFaz)|jU> z-|xpQ;)>NMFo9OVmByODbU&@>Pfi|4lTq#6CPSL0YZ>`wy_2xR8oQ=2+Zcz(rv%`}g0H(Jqj5-XZ_`NtT04L2s5@QCRb&_(e=-#UvGl%KNmN zW#CeE`bB~?rFd+ZY@~i$=+>h+-{8{bKFEDIIyY|k+nkxJe*5#sktSH>kAl$P$jJJr zcOVdizv<-)w@Jck)0PV_O80yzbHmhgcuU_=$Y`_mA$M){q>=f6K-dSLwP$r>uSv83 z5A&jb8xOD;qz9ssSQmCZdwPGkAwxEfU&l|R0ms$?h}pC+m&=o zy#sDW8ZOD^gK5i2NNzj)Gj4kjO5-*UrOD_hr4h1%ME_zUy3X$^)5qtw1^ssw!%?)Jd9motcOj$@%itZ8ooj<9j#>&l6L6Rbp1MA*t6#X3q?{fa~+ zERtceRJA^=z{cRf6v{Y_qFWcMYa?r>=;cWFGZ+$k1UJ%mPZT&dK zQZT3GNza-_T3_tP{ij`m0};!4sE-m{ZPUMqeX3&u?0nQZ*yuoZ{5QTz7h^rgwM#uT zG{hJwYJd29EN_>-Gjo5c#)W`ZREVE{Y2qQhu7Hj(Sxct{63&rFwP$R2SGK(mytW#h zg_7JuiOCHe_s9%pe2zB={u~)X#b(_F9lVVbKh3ax3=N^E$deknu`)3y7%0!lr zHLEKaINaizng{tDw$v;A@^oIi-&q6&1nSucK2WLHSXqgku4YCoxSE?iL=bXOzeW(2 zWu( zQQCI~G!mi=wRN^99e&V=IEE-J-Elh}1a7qVX4OjVH$FA!d+MTiXg9}9vxMG$?2rt`r#x+vCXTLr;s|!rc%6eBooMXJUgSP6;$hZwK z%?279b-Dwjk2&uITecDeYdO9&Ak2m0Ti2?W|3s)gi~lk7>{s*%7$udRBvtouoBEPQ z1`6K9T~b$Tzb}3nVBXn0I1Ij(Klxmnd{3qhtZD9t9i_Lr#E#8nrmu}h{silU{WMcX z#hj2r1^Rn)(;IN=fQ#<2(@NgdeIEa}766NQ;_~9m!29IYy~A$4AHOFmwlLkU|0bHI zW}GJgKQtmSVn?p{$_Kt>TxsE1M#J@pUv7Qpub>HdT&++ATk*J;+lsMQJ^K0e+qc@J z7<@b8$W7G~VKRgo>T!Z7;U03Jj4a+JUXjh}-BED?VFST4ONJ$=9I9^xh!RTwcw9$_ zjR)BJad9ErSxIU{vCow^fk$QJgzoJ=7IV-C;|s8L>i4qx*)L{~E4^oPUd!AI>HG-e zDX@HH#6K(Wi|x1Y&awJ15aJ9NL@wSvn3PMRFH`pjbV$XkszW9oMvQ>Y`@ zW^b^SQ7H0Wpo}3lf+v>#=hT!JAO=9dLYax;tBdVwUq;3uen?5;`yP#iRI6#`Ad^6; zdy=IRri0SR42beVEgyZ7W}xF!EhV$|{Lf!yNJWET3gY|}Vbz<3wJt_uF=@4}860oB zW6R(@6M{1QBJY|RecO67B31+Pha*2_c1+Sc_spX{K81`)Dm*I*`P?3kI*=YAafHU52f(nYkR4bc z0KzU}AuCaa0W&N=$Ho#-C=u7~SwMST%*cv*9R3EB+Q4gpt*M%oSBf9B5NGq0d_^kj zERH9+kBI%ls7vo@jaEHTR;)G&z2%Bm)fymt4HbgY5cCR-+7ETwEL+94st;u^XIpX2=9D zFWLb|VN$BYub%H(`@cl|y6J9e+HOa^XG5PN=J9o&TBm$F1JW!ud+%-K1;NZUqK!Z0OpAe8;0IiFr-7PV%36HaLraw0~ zyl#BRo(N#l*%j`^w{!q(><)rcc0xc;3Be8fL(hbzsI1@O0?j*nbR4Hd6q+}&aG*Fv z7A^(&BAXMygvTei!VB_-+6W%59^41OSzR;oda6HTfH0^8>@o3mD-Rn$>-l+L@(v^5 z#N`r2IN$7L2k;*$B(9o1Tn{=nCT6~Ajgdw}HU2Q@xFZ3;-5AO_GxGn36C)R~9Ys=> zmX^lG#_qUv_x5sebGwM7EXMDdprZ`W&#}U8*etNyKK}!dV<7bYeDMnqUH|gse*Cf_ z82e?UWW|J@t}ZGyRbN}%mfBR0He!K;gF{(Wm79~(sb=bT-J>GezelsE0o_kFsnfI( zsoTG$t=xr71)IR`Q8hgP$A+oK zWF{w!+qX=vWBFAQ00IK#wSOl`zja`*?M znRBU#biZXUqI^9gR=@FV5_CmwJAYEDQg@Yci)dbuP9IzYSd+vKj#v=oCTO>-j4LlM zUt3!nsC|Zph>G1%lfVZ7(Gn><;eN16Deb;c7gWCcA+f%yy5L6-%`f-OK*(ox`nYjw zMw4{$Hf}=W=CmZ!W~rGY6hzwk`oIww`ZE!!bhQwp4N~ozYXG5BvO!p z5@L=dNf&no>a?;a$MbpAjXhyMjV_s5y3nnwDj8rBP-mX#seXP=9^@!h!i}2EM`?F| z4HDBk;FeG@9g6VNmyODs3)1?1rKC4yfhcR*3>~HImg%JLDp^a_8Qp!JpVxtjr!$Qa z;Vd)zQkL8q^&A=%8$G~P*?-Lm0tcs0p2&?X(rGpPOj}B9K-y62=s@k0VQ8O4xu%v@ z)#G+M6I0WZvTK^NUI(Ar5mFSv`p#EN$k)*jul-I)5Y>41a@_Rb=}dYo`p%YL*oL3~ zT&Ntud*jG_=Rr@9`+}7ID-DY-nNx;mq-CmFPdgnWb0}cab27e5zSUq**n0}=Pdruq zArGYy^zUNlJPateSE<+{R}0=XdQQ=lOk~|JrNFXYy*Dg%NgWZ(@$&Qb{*=tz=Op<_ z=sm8(+o9cr?-LVs3=ED0U49MaCMG8K^dRcX>g&b%_*!xTRV1O`jNq|~ZyH_s(Gepr zN`|PgP^+z_u$-mtLWl{=7f9tN!_m-jSYJ0 zV=v^*b3)7vmuD~CHNhS-+U>O^&Dwt|*lw02)_nU8&X3xq$`Xa`YWz`Y20k!|ziE7Y zyt}(QTPZStsgrS>U2FDNKWUsTvK&mz*Z_X z9)Onj^E9%}QFbcpl5wzE4HZ_f z<8#;+PCAo3)qFS!=F+{}SvyaZ2#xxy+__!_lFye;BoWchtGi=sdtd=hdQrpWxMh(} z_=~M{i?>0pF$_^bFg1;@O8XXVaM#!tPN?wcg1uVi?@22tNi!^PmT(}kv8!9!v6OE044|g7UDE%BL@xRBhjuMtCe=@3}Gyb;EO4lUh{Ke^)@@ncxSvWZUz=qx}Hbp$wgq+dh{B{-|HVE+qAF$os1%Lr1AxGk0YT7SpWkg#jQie4S!Ozyhqo zf#FWeBoA?b;da2NzWl%+aNwJDlN$X8F!~cdFv^_w?rTfHG=JvHp;y1Iya)s0CnlVp zA8!_4!T!(UzA%Lth|x2^cB`cbW4^nNU_A#$!7bT`g9DZV|B9(QscYq5$y2r{; zVDx-15m9Qw!4`vDz+0v35(l8V%}W5NX~Z-SjA63#U{7ONyZ!Vi3Rwy1Ij6_1(_&Q^`=?U zP#sv|TjHP^jO`ET$o-o15L!mXe`Z$3@&WQPvok@?L#UNd;R>0U>kNL2fwTj#(|_z$ zA9#WCOI1nCL#P^kbYvhIj|!h23%!)2dOAzTq>~Bjr&m+s#nCT5!PF?QU-|+(qL!0M z%mNmC$@9L{<^vw8(R?Hj`;N6+BLP1gNe5jdAp$n=NB;u{HvgNaZ>0<_rLn~hAHVQ6 z7%Z5yiaNI{JRD8JKVj4Qy}^lAnpL@+>En>n_;%C=5uYzuZZlNKG2)tQ`8!}JiJiTt zTAP>0N}>IUkhRgPfc&zjz8|R+Fp$^b`;Znh))*3Cxwlx(0zKK85DWf!m5bBVWzF)> zMU^`B!Jn3-RhcM%*%{O-v9gWLd%W8K(%cTL5za2R8S=;KAuO`8hYu-+5+`xb-Hku( z9Z;ThKaD~IL9gYIZ6d>)8aNafZ|$HpoCZPf`iz^Puf%s=8%JKFQkOVA*r%GQGgoG} z?3CEwTc^px0-5>ikudRV1hl4I5OGC>YK+B3_9i)T7TMx}dF6m9@iQ}&)qyE_X@8Ev z1@9wfJtlKrxjTp%h}L+>`a1mz4jCo&D_A>~F$X#Op#}v+i?{)xdIl1{MwS01h`(9G z3J42IZht-t@~JW6hIFua>;7<2BHlVZ!Utchm!mJ+#8lOdcv@1tTL*iWn2t$Zd`KQ; zhvYhw0f#SiEiE^e4mi!^e(g$LXg}D!WqOLKk6`m&&a_z{Z9|9F7ET9E5bT0BdgV^t z%$Cv!-Rp2te`^&kO*6#o`2Feoj#Mmrn;yjIJ#!v!18c67)_CAj>R)9pu4j+6KW((3 zN+@|o^d#=z$E4oN%Y00w@C!M0_PDcGT1}Gv`W`Ee;U6r}0|g$Y@DF&`?to#*zbI^a z*)FVm<4k?~_o>Xkm5aTpx>F_t&i@ZYx~^Lhl3%~Z@;94gdi7u^^7>jQ_lNM_w0;Nf zF=253e=Z8miW$ufI>-8##D?%uKE8DW`*nbmn&-I&j%X;uk_GC58@Fg4p!A=sV<*Sh z33v!-U>Kl#{<#^-&dHfhK%=kZwfsbv(EMt@CcW3};hpQ&#+U!*$@d6{GC)hB5r$k- z`j>;>?u4^q027E4tvZSTNU*fDlyCQaN{XJo{-&eI$-Y3SRv*}o;`Xa(U`J6eW0${L zLoT^qSbqsL7M4n%*x=&iL;xV3gF`5ylnX$rWMpI?b8-^mK0h*CM z(ri+_latSr&x`?$wP6U9U>8g~y4RIaJu+)pfv*qr2RNTzct=OI{0drPITEM}lw1Yz z#ZMMK4D7=vX4MEfd=tBp?jrCJ-tp|?M^{5};T17+ZLzcuDmNmR+< z6BDy4BovC+s}-egH^K#cgu%hVY`2Pk-742xY#JX1uC>|vfqOwe?cjWX0h8>^J)(2Y z_q+ptW>5WA`^e8X%NNi2;c5`(y^kK3FMzWFuzIp8@m!fG2T?Kbsjcm1#!dN`a*|es z8ue=FK(WWIL^gs5F^!J#;v_hRWw(zcXPN<)5RfWp8|6M}D{kQ9P&2y3>rP8a;UC4| zdS2N`oB^=Du&@O4{{yW7&g{d9mOb53l7$ctvP6DpN2}?{FW)Lc&NrRP?nRQXD{#Fv z0(zbZ=8w4#>M;mNMfFl_tYw3CzWT10_u*(pI7*7qB_-(pst)@_5^{&8#>m9JGbAz+sAR!^kr-j+)r_FFwS9$jZXY1 z4-pfy&X8YdPUK%~kf8vuJ~(i?$_DD(db+z`TU+mD9~>McCf*U}bOilZ3F3$$0)*3^ zL(Oh2sWv6}MN$4js`J%!lLX{p4>M5QZ&$j6PD*Od{Px}?@GhdTsSB;J zqmS)z>iEbhy$DNsac?w&Jpm!uEdAA8-eV|cNtjIQ?!lQ?5PLVw9K=2T`cZqX#27k_ z)M|6+Z$t(3sUx}9`V z`gmf;8m$K$t&79;>-wmdKVz}WYZSOgk3=p+7wq#tZdICF42m;P`OM2ODw6eKmvXT> zxfs>wficf|fJT{bEQB1g9q)jXtx%dTi_zo)ZD)6|9xSQCQY$m^xtEe}|uJ(Es+9Sqf+ z+V(^eJ}@LR)&@y$x32H>n2#Kc`@&i42sYO<_|c7fId7f4_U&eFA->#VI6S*B-sQ=g ztqFOW$@lHSEkHweD`vsh%HZgBwVsm&f|PF_dX-i!O1Q%c1(P;y$R1l6Tj|(Fb_HVe zJgSp)P9*8!Vl=_;wxb*1KhY5u!JdnB`tEhNUyq-j8_(K#-BsGe^qbQZ{FS{5>vJgq z3|0YbvWm~vLuoDbRp_0?Pp~kLoMK_N8$(Iw5g8MOo5KAI;%OIliJK>bmopsh2DHzK z|3T$wh{hO&L}CDt^B%xn>@fPy2uweds+R5m>iuo5?I5}|T7yn34BGq^KqY8=_%|`Q zE1-wjNVDlpPaiJd;G3Rie(*%Xul#_Ses6hG6MO;`h)b4|i4Q&ml;@QLx+oX~bo?*E zZF~;cEucvHI{x(+-=C!6{w+@Z3-8gFDQ_&~B_OPS(S66&b2I_~-J=0R|AKp-Q8aX* z<8I`?u>Iu<$QAo~k^e$_->Ms%4ZLsoFS6g2Ab&eMSIQ!B-Ym%Fec2Hh;$v{!)@rKi zE#znS1ON~89zgFuNd9F$#9a)i%j6%gdo36H8n9pLuM0;l1uYl_-qBJv9Ga3hY_Di- z`Fw=#Y~lKNABDCeb>QIL^MZc-nlr4D^b)nfV8e<2J6^w0lK0Ry zlJNM_P_ujTTmiYrIv=~e+=i3)h1XOSr&8WEknT70IAixFXOgl{uh6Y(txkw`<=w`o zTfK*ahLrq=%(psFp&s`-E^?3`%M6n5An9bwzHGMtcHQO{wc zG8oA6b~n?qp;Y72-gi19)a-EiWp8h9lGc^#HxYCvWS=u4xaIfJ*^AK}G9{>XiW%ko zL1Gat878R#IMsXnK`=uDy$ClC*GtW7IQB>CjEBI{kZu;!9p#T}QYv)^%a2?FXIP?C zqYim}xwb}_O`-!yeQb5VC}=3JqYB>2_qjXBmXByKPdv2THPR%1xg7JVBidE&>oQ7B zJp*?fOOppVlCP{<{IDwAdDg#tsk?Dd60sfZv$s>1lJd-D^D>y+8gB?34s(iiFV;+% z$Z}x;rG7nFC|_mdE5z1fri>W^57Ef>yAdhOtCIrq_eXRoa$sso4JhBGB(H}AyNcb} zZcP||(HQzYgG+kxIsLsNlD3iVs_!V(Kl|klF)`I-J>l3lQ&`;KOh3QRx&0iP3pp3B z?3#TYZtQGjzjuq08k?Nzt`ur^G;O21=&-@vasAR%#KY9aS>mF`c~#9@{qf{i3{b7csuR6SMkXSO9 zaGEA{*TnQBgb%%312m6KZ(n zff2OUrB8ftVr=Z>IHu;og9i!XmlHl`zYp%hkpZgTGF$ARl-hJ1&N&X`$1+iUY$bwc z(u~GOA3F$$pS{#^&B9;vKTIqvS@}qXOw33H^KVo{rS|fP$yfXRm;FgFLcWju4eO>t z3s{i%yUb53yJ|(7tI(E0LZ`EDy&K@JAv*dLD)CnKeN=6?)yTlm-LasAl!@DdWQT~7 zh+X8dVzr5GW&+3X$Dmh;({$<0gK@;p%e>sDD1o8K37iV?Cw}}*7zOK8yJ5G|E*m@= zF0|-SeulFm$a%x65S7SgW#sQl)=YoHh?UCjJnyS2QN7(mBF5bOJl^UWWy=9{yQKF$ zU-BN4I^7*a@w`~<((d+lW=>&U+Y5dXsaB`?Ob8Qn{~zT}kpQDtG@PE)WpZ6{M9{l0 zXI~h&r1GyljDP$(H`~{hIA4%*->JQ^YJzjkr(LaX-ms+dT|A;c6E;|N=iNNo1^n~0 zLSI>W-BU-Ehp%+_cohdLXFIgPzzfGChW3w;O`nf@Kbz(0W~w~4!>yDEzYTyHL@FX3 z^M{l=6^CW!houkn9+pC#f@}1;qwL)}A-@rWBr|-I516KYvQHi4lj|TA>6l9$)?;UJ zJF#Ytl(I2OJ_a;fk9WN7bJNTO_QH7W_uZIGAZcM3@QnKDfQ;`bKpA6{x@c2wq;#P^I~Lj^H99>e@W6F6 z;cuX9oY9Tpa7Qp|zHw8_H5Qt~)roc!9r?$sx*BE*-#DKCz1=|S%E@Ai63IhZf|F_p+p}(OsqFXGVf$?ZJ5s!a> z!U8gU85ocDFE#>BbwE?-y=#>5f1h08q7t_}pZ(WYV;X7H!n6ll*WY3jGqrJhX0x;a z9l)cNe>fc7BbZci%EywK<;zTBqT&tGQ7iFCSD*X;%dG)<%>Rf18t|gZp}oKKg0WrV zB&IdVz~gdXtKr1tTSJkUYz4XrSjCN5Ck*?hNe!x?bB0SBC%jEuTnCn0!22yhw! z7Zk`?O<7miO34c0{S#*-ypJ8`n#){)S5N-YpBoJge0cqT0S?dC;<~0*sZ5vbk1T(r ziQAvYjO41mpdgVMm`s=tAn^uED>nUh#J zQkU3F!0bWBGzoi|CafrNoztm&2dMHJb`nn{;f8^iwG%&-xdu0trbu~Ujffz7U*qPi z_rz_!c|NL@qf*3jp;fR)7W%ZyB7|+`O-gdY$M5MsZ6Re_TfB>SVU0iv=k&C?VI)^0 z$Hc@0cmsDbaC&;$-Hn$;m$)9E;`h&cNeXzLyng@dd7iE<@*|tZq=`F5p?QY1)eFDv1iBKh9F>zAI7U^^*RwEp)@_fqpMA+rR$ zgp;0I#xhL<;F9<#%+o&nw~Ek!G43X@_+Y~G2A%zS(Vv3nCjAtv=&*Z$bAhhPu%s|s z5#H4w8Nxt`e*Sf58fSo1x)%+A$*Z|JwpxJh#~FQP>k0>N3r6wMW5XW{11eoEDboHK zt`EC#g&s!xfFzm^dzF1wpsxlH&7==^vnUY|v^j`O-H>3vl4PjUCp^9?&G}}aI97hVi60N@X`f*?=z61PEh%*qlnPhK61kxcdZp)K^u8oH>5Wfd)JCOm*RCauy hX?Ja?BssvB68_+cwZnU)j%dI~QC8()p|qjj{{ch6GqV5y literal 0 HcmV?d00001 diff --git a/zh-cn/application-dev/napi/napi-faq-about-common-basic.md b/zh-cn/application-dev/napi/napi-faq-about-common-basic.md new file mode 100644 index 00000000000..1e18f9fa044 --- /dev/null +++ b/zh-cn/application-dev/napi/napi-faq-about-common-basic.md @@ -0,0 +1,313 @@ +# 常见基本功能问题汇总 + +## 模块加载失败,报错信息显示`Error message: is not callable`. + +- 问题描述: +通过如下模块注册代码提供的libxxx.so,在部分项目中调用动态库的API,出现`Error message: is not callable` +```cpp +static napi_module demoModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "xxx", + .nm_priv = nullptr, + .reserved = { 0 }, +}; + +extern "C" __attribute__((constructor)) void RegisterEntryModule() +{ + napi_module_register(&demoModule); +} +``` + +- 排查建议: +1. 可根据以下文档进行排查: +[ArkTS/JS侧import xxx from libxxx.so后,使用xxx报错显示undefined/not callable或明确的Error message](napi-faq-about-common-basic.md#ArkTS侧报错显示undefined) +[模块注册与模块命名](napi-guidelines.md#模块注册与模块命名) +2. 同时也可参考动态加载能力是否可以满足该场景 +[napi_load_module_with_info支持的场景](use-napi-load-module-with-info.md#napi_load_module_with_info支持的场景) +[napi_load_module支持的场景](use-napi-load-module.md#napi_load_module支持的场景) + +## 在大量需要调用ArkTS方法进行通信的场景如何保证异步任务的有序性 + +- 具体问题:在大量需要通过c++调用ArkTS方法进行通信的场景,如何保证异步任务的有序性? +- 参考方案: +可参考线程安全函数来实现,napi_call_threadsafe_function可保证异步任务执行顺序, 需要注意的是这些异步任务会抛回到ArkTS线程顺序执行,如果是抛回到主线程,异步任务的执行时间过长可能导致应用冻结退出,所以不建议将长耗时的任务通过线程安全函数抛回到主线程执行。 +[使用Node-API接口进行线程安全开发](use-napi-thread-safety.md) + +此外,napi中常见的抛任务方法的差异如下: + +1. napi_async_work系列接口:只能保证execute_cb执行在complete_cb之前,对于不同的async_work,时序无法保证 +[napi_queue_async_work_with_qos](../../application-dev/reference/native-lib/napi.md#napi_queue_async_work_with_qos)是在普通napi_queue_async_work的基础上,支持自定义qos优先级,而这里只是指定libuv调度任务时使用线程的优先级,不是指任务的优先级,所以也无法保证任务的时序。 +2. napi_threadsafe_function系列接口:接口内部维护了一个队列,是保序的。 +napi_call_threadsafe_function:先入先出 +napi_call_threadsafe_function_with_priority:根据自己指定的任务优先级执行 +[使用Node-API接口从异步线程向ArkTS线程投递指定优先级和入队方式的的任务](use-call-threadsafe-function-with-priority.md) + +## 是否存在便捷的回调ArkTS的方式 + +- 具体描述: +在进行多线程开发时,ArkTS函数只能在其创建线程上执行,c++线程不能直接通过napi_call_function的形式直接调用ArkTS回调,是否存在便捷的方法? +可参考文档: +[Native侧子线程与UI主线程通信](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-native-sub-main-comm) +[使用Node-API接口进行异步任务开发](use-napi-asynchronous-task.md) + +## 如何在C++代码中回调ArkTS方法 + +- 参考文档: +[如何在C++调用从ArkTS传递过来的function](https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-ndk-26) + +## 由于工作线程 A 长时间持有锁M,而主线程又在等待获取锁M,形成循环等待条件,导致系统进入死锁状态 + +- 参考方案: +可使用napi_threadsafe_function系列接口,具体可参考前文问题二的解决方案 + +## 如何确保数据类型的正确映射与内存管理的安全性 + +- 具体问题:如何在遵循N-API单一返回值约束的前提下,安全、高效地将多个返回值(包括结构化数据和指针信息)传递给ArkTS运行时环境,并确保数据类型的正确映射与内存管理的安全性? +- 参考实现: +尽管napi_value接口仅支持单一返回值,但开发者可通过该返回值封装所需的全部信息。 + +比如通过napi_create_object,创建出一个ArkTS对象,用这个对象来承载返回的所有信息,number和string都可以通过napi_set_property/napi_set_named_property等属性设置的接口设置到这个对象上。native对象也可以通过napi_wrap接口和ArkTS对象进行绑定,后续在通过napi_unwrap取出来。 +此外,亦可使用ArkTS数组作为数据载体,其具有良好的灵活性。 +- 参考文档: +[使用Node-API接口进行object相关开发](use-napi-about-object.md) +[使用Node-API接口进行array相关开发](use-napi-about-array.md) + +## napi_get_uv_event_loop接口错误码说明 + +在OpenHarmony中,对使用非法的napi_env作为`napi_get_uv_event_loop`入参的行为加入了额外的参数校验,这一行为将直接反映到该接口的返回值上。该接口返回值详情如下: + +1. 当env且(或)loop为nullptr时,返回`napi_invalid_arg`。 +2. 当env为有效的napi_env且loop为合法指针,返回`napi_ok`。 +3. 当env不是有效的napi_env(如已释放的env),返回`napi_generic_failure`。 + +- 常见错误场景示例如下: + +```c++ +napi_value NapiInvalidArg(napi_env env, napi_callback_info) +{ + napi_status status = napi_ok; + status = napi_get_uv_event_loop(env, nullptr); // loop为nullptr, 状态码napi_invalid_arg + if (status == napi_ok) { + // do something + } + + uv_loop_s* loop = nullptr; + status = napi_get_uv_event_loop(nullptr, &loop); // env为nullptr, 状态码napi_invalid_arg + if (status == napi_ok) { + // do something + } + + status = napi_get_uv_event_loop(nullptr, nullptr); // env, loop均为nullptr, 状态码napi_invalid_arg + if (status == napi_ok) { + // do something + } + + return nullptr; +} + +napi_value NapiGenericFailure(napi_env env, napi_callback_info) +{ + std::thread([]() { + napi_env env = nullptr; + napi_create_ark_runtime(&env); // 通常情况下,需要对该接口返回值进行判断 + // napi_destroy_ark_runtime 会将指针置空,拷贝一份用以构造问题场景 + napi_env copiedEnv = env; + napi_destroy_ark_runtime(&env); + uv_loop_s* loop = nullptr; + napi_status status = napi_get_uv_event_loop(copiedEnv, &loop); // env无效, 状态码napi_generic_failure + if (status == napi_ok) { + // do something + } + }).detach(); +} +``` + +## Native层调用ts层对象方法必须传入一个function给Native层吗 + +- 具体问题:【NAPI】Native层调用ts层对象方法,必须传入一个function给Native层吗? +- 参考方案: +如果想要在Native层调用ts层对象方法,则Native层需获取该TS Function对象。 +获取的途径也有很多,比如: +1. 通过传递的方式,ts层传给Native层,也就是问题描述的方案 +2. 可以把这个ts function通过属性设置方式绑定到Native层可访问的对象上,这样Native层通过这个对象也能拿到function进行调用 +3. napi层也提供了一个创建ts function的能力,即napi_create_function,可以直接在Native层中创建出来,这样,Native层自然就能拿到这个ts function + +## 是否能调用ets的方法并获取到结果 + +- 具体问题:在c++通过pthread或std::thread创建的线程,是否能调用ets的方法并获取到结果? +问题分析: +如果是直接创建出来的c++线程,这个线程是没有ArkTS运行环境的,也就是该线程上没有对应的napi env,所以无法直接在刚创建出来的c++线程上直接运行ets方法并获取到结果。 + +- 解决方案参考: +1. 使用napi_threadsafe_function系列的napi接口,这系列接口,相当于在c++线程抛任务回到ArkTS线程执行ets方法 +[使用Node-API接口进行线程安全开发](use-napi-thread-safety.md) +2. 在c++线程创建出ArkTS运行环境 +[使用Node-API接口创建ArkTS运行时环境](use-napi-ark-runtime.md) + +## 是否有不拷贝的napi_get_value_string_utf8接口或者能力 + +- 具体问题:当前napi的napi_get_value_string_utf8每次调用的时候都要进行拷贝,是否有不拷贝的napi_get_value_string_utf8接口或者能力? +- 问题解答: +当前版本暂不支持该功能,每次napi_get_value_string_utf8都是需要有一个拷贝过程的。 + +拷贝是必要的,因为会涉及到string生命周期。当触发GC的时候,ArkTS对象可能会在虚拟机里面被搬移,可能搬移到其他地方,也可能直接对象被回收。如果直接返回一个类似char*的地址,对象被移动或回收后,那这个地址的指向就不再是之前的字符串了,此时再用这个地址去解引用很容易崩溃。 + +## 鸿蒙多线程下napi_env的使用注意事项 + +- 具体问题: +鸿蒙多线程下napi_env的使用注意事项是什么?是否存在napi_env切换导致的异常问题?是否必须在主线程? + +- 注意事项: +1. napi_env和ArkTS线程是绑定的,napi_env不能跨线程使用,否则会导致稳定性问题。 +[多线程限制](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-napi-process#%E5%A4%9A%E7%BA%BF%E7%A8%8B%E9%99%90%E5%88%B6) +2. 在使用env调用napi接口时,需要注意,大部分的napi接口只能在env所在的ArkTS线程上调用,不然会出现多线程安全问题。 +参考该文档的第四点【multi-thread】 [开发者使用napi接口时,跨线程使用napi_env或napi_value引发多线程安全问题](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-coding-standard-api#section1219614634615) +3. 最好不要缓存napi env,否则容易出现多线程安全问题和use-after-free问题 +参考该文档的第八点【use-after-free】[开发者使用napi接口时,跨线程使用napi_env或napi_value引发多线程安全问题](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-coding-standard-api#section1219614634615) +4. [napi_env禁止缓存的原因是什么](https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-ndk-73) + +## napi_call_threadsafe_function执行顺序不符合预期 + +- 问题描述: +napi_call_threadsafe_function执行结果不符合预期 +原本期望的执行顺序是 a -> b -> c +posttask(a); +posttask(b); +posttask(c); +但是实际的执行顺序是 b -> a -> c + +- 排查方向: +1. 是不是用的同一个napi_threadsafe_function,若使用不同实例,则无法保障与调用顺序一致; +注:对于同一个napi_threadsafe_function来说,napi_call_threadsafe_function是保序的,接口内维护了一个队列,先调用就会先执行。 +注:不同实例是先创建,先执行,但是若使用不同实例,得保证对应关系。 +2. 是否能保证实际call的顺序是a -> b -> c; +3. 使用了napi_call_threadsafe_function_with_priority,该接口是向主线程的事件循环中投递任务, 由于主线程存在不同优先级的队列, 不同的优先级的任务是无法保证时序的,同意优先级的任务,由于是在同一事件队列,可以保证时序。 + +参考文档: +[使用Node-API接口从异步线程向ArkTS线程投递指定优先级和入队方式的的任务](use-call-threadsafe-function-with-priority.md) + +## ArkTS侧报错显示undefined +具体问题: +ArkTS/JS侧import xxx from libxxx.so后,使用xxx报错显示undefined/not callable或明确的Error message +1. 排查.cpp文件在注册模块时的模块名称与so的名称匹配一致。 + 如模块名为entry,则so的名字为libentry.so,napi_module中nm_modname字段应为entry,大小写与模块名保持一致。 + +2. 排查so是否加载成功。 + 应用启动时过滤模块加载相关日志,重点搜索"dlopen"关键字,确认是否有相关报错信息;常见加载失败原因有权限不足、so文件不存在以及so已拉入黑名单等,可根据以下关键错误日志确认问题。其中,多线程场景(worker、taskpool等)下优先检查模块实现中nm_modname是否与模块名一致,区分大小写。 + +3. 排查依赖的so是否加载成功。 + 确定所依赖的其它so是否打包到应用中以及是否有权限打开。常见加载失败原因有权限不足、so文件不存在等。 + +4. 排查模块导入方式与so路径是否对应。 + 若JS侧导入模块的形式为: import xxx from '\@ohos.yyy.zzz',则该so将在/system/lib/module/yyy中找libzzz.z.so或libzzz_napi.z.so,若so不存在或名称无法对应,则报错日志中会出现dlopen相关日志。 + + 注意,32位系统路径为/system/lib,64位系统路径为/system/lib64。 + +| **已知关键错误日志** | **修改建议** | +| -------- | -------- | +| module $SO is not allowed to load in restricted runtime. | $SO表示模块名。该模块不在受限worker线程的so加载白名单,不允许加载,建议用户删除该模块。 | +| module $SO is in blocklist, loading prohibited. | $SO表示模块名。受卡片或者Extension管控,该模块在黑名单内,不允许加载,建议用户删除该模块。 | +| load module failed. $ERRMSG. | 动态库加载失败。$ERRMSG表示加载失败原因,一般常见原因是so文件不存在、依赖的so文件不存在或者符号未定义,需根据加载失败原因具体分析。 | +| try to load abc file from $FILEPATH failed. | 通常加载动态库和abc文件为二选一:如果是要加载动态库并且加载失败,该告警可以忽略;如果是要加载abc文件,则该错误打印的原因是abc文件不存在,$FILEPATH表示模块路径。 | + +5. 如果有明确的Error message,可以通过Error message判断当前问题。 + +| **Error message** | **修改建议** | +| -------- | -------- | +| First attempt: $ERRMSG. | 首先加载后缀不拼接'_napi'的模块名为'xxx'的so,如果加载失败会有该错误信息,$ERRMSG表示具体加载时的错误信息。 | +| Second attempt: $ERRMSG. | 第二次加载后缀拼接'_napi'的模块名为'xxx_napi'的so,如果加载失败会有该错误信息,$ERRMSG表示具体加载时的错误信息。 | +| try to load abc file from xxx failed. | 第三次加载名字为'xxx'的abc文件,如果加载失败会有该错误信息。 | +| module xxx is not allowed to load in restricted runtime. | 该模块不允许在受限运行时中使用,xxx表示模块名,建议用户删除该模块。 | +| module xxx is in blocklist, loading prohibited. | 该模块不允许在当前extension下使用,xxx表示模块名,建议用户删除该模块。 | + +## 接口执行结果非预期 + +问题描述:接口执行结果非预期,日志显示occur exception need return。 + +部分Node-API接口在调用结束前会进行检查,检查虚拟机中是否存在ArkTS异常。如果存在异常,则会打印出occur exception need return日志,并打印出检查点所在的行号,以及对应的Node-API接口名称。 + +解决此类问题有以下两种思路: + +- 若该异常开发者不关心,可以选择直接清除。 + 可直接使用napi接口napi_get_and_clear_last_exception,清理异常。调用时机:在打印occur exception need return日志的接口之前调用。 + +- 将该异常继续向上抛到ArkTS层,在ArkTS层进行捕获。 + 发生异常时,可以选择走异常分支, 确保不再走多余的Native逻辑 ,直接返回到ArkTS层。 + +## napi_value和napi_ref的生命周期有何区别 + +- Native_value由HandleScope管理,一般开发者不需要自己加HandleScope(uv_queue_work的complete callback除外)。 + +- napi_ref由开发者自己管理,需要手动delete。 + +## Node-API接口返回值不是napi_ok时如何排查定位 + +Node-API接口正常执行后,会返回一个napi_ok的状态枚举值,若napi接口返回值不为napi_ok,可从以下几个方面进行排查。 + +- Node-API接口执行前一般会进行入参校验,首先进行的是判空校验。在代码中体现为: + + ```cpp + CHECK_ENV:env判空校验 + CHECK_ARG:其它入参判空校验 + ``` + +- 某些Node-API接口还有入参类型校验。比如napi_get_value_double接口是获取ArkTS number对应的C double值,首先就要保证的是:ArkTS value类型为number,因此可以看到相关校验。 + + ```cpp + RETURN_STATUS_IF_FALSE(env, NativeValue->TypeOf() == Native_NUMBER, napi_number_expected); + ``` + +- 还有一些接口会对其执行结果进行校验。比如napi_call_function这个接口,其功能是执行一个ArkTS function,当ArkTS function中出现异常时,Node-API将会返回napi_pending_exception的状态值。 + + ```cpp + // 接口内部实现,经校验可返回状态值 + auto resultValue = engine->CallFunction(NativeRecv, NativeFunc, NativeArgv, argc); + RETURN_STATUS_IF_FALSE(env, resultValue != nullptr, napi_pending_exception) + ``` + +- 还有一些状态值需要根据相应Node-API接口具体分析:确认具体的状态值,分析这个状态值在什么情况下会返回,再排查具体出错原因。 + +## napi_wrap如何保证被wrap的对象按期望顺序析构 +问题:在使用`napi_wrap`把两个 C++ 对象包装成两个 JavaScript 对象的场景中,由于这两个 C++ 对象存在依赖关系,要求其中一个c++对象必须在另一个c++对象之前析构。然而,JavaScript 垃圾回收(GC)的时机不确定,直接在`napi_wrap`的`finalize_cb`回调里销毁 C++ 对象,没办法保证析构顺序符合要求。该如何保证两个c++对象析构的前后顺序? + +参考方案: +先标记可释放状态,当A和B都为可释放状态时同时释放C++对象 +原理:将所有依赖对象的释放逻辑集中在最后一个被销毁的 JS 对象的 finalize_cb 中处理。 +实现步骤: +在 jsObjA 的 finalize_cb 中标记 cppObjA 为待销毁(不立即释放)。 +在 jsObjB 的 finalize_cb 中标记 cppObjB 为待销毁(不立即释放)。 +jsObjA 和 jsObjB 都为待销毁状态时,按顺序销毁A和B。 +示例代码: +```cpp +struct ObjectPair { + CppObjA* objA; + CppObjB* objB; + bool objADestroyedA = false; + bool objADestroyedB = false; +}; + +// jsObjA 的 finalize 回调 +void FinalizeA(napi_env env, void* data, void* hint) { + ObjectPair* pair = static_cast(data); + pair->objADestroyedA = true; + if (pair->objADestroyedA && pair->objADestroyedB) { + delete pair->objA; // 确保先销毁 A + delete pair->objB; // 再销毁 B + delete pair; // 释放包装结构 + } +} + +// jsObjB 的 finalize 回调 +void FinalizeB(napi_env env, void* data, void* hint) { + ObjectPair* pair = static_cast(data); + pair->objADestroyedB = true; + if (pair->objADestroyedA && pair->objADestroyedB) { + delete pair->objA; // 确保先销毁 A + delete pair->objB; // 再销毁 B + delete pair; // 释放包装结构 + } +} +``` diff --git a/zh-cn/application-dev/napi/napi-faq-about-memory-leak.md b/zh-cn/application-dev/napi/napi-faq-about-memory-leak.md new file mode 100644 index 00000000000..0795403552f --- /dev/null +++ b/zh-cn/application-dev/napi/napi-faq-about-memory-leak.md @@ -0,0 +1,218 @@ +# 内存泄漏相关问题汇总 + +## 请问当前是否有机制来检查是否有泄漏的napi_ref + +- 具体问题:napi_create_reference可以创建对js对象的引用,保持js对象不释放,正常来说使用完需要使用napi_delete_reference进行释放,但怕漏delete导致js对象内存泄漏,请问当前是否有机制来检查/测试是否有泄漏的napi_ref? +- 检测方式: +可使用 DevEco Studio(IDE)提供的 Allocation 工具进行检测。 +[基础内存分析:Allocation分析](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-insight-session-allocations) +napi_create_reference这个接口内部实现会new一个c++对象,因此,如果忘记使用napi_delete_reference接口,那这个new出来的c++对象也会泄漏,这时候就可以用Allocation工具来进行检测,这个工具会把未释放的对象的分配栈都打印出来,如果napi_ref泄漏了,可以在分配栈上看出来 + +## napi开发过程中遇见内存泄漏问题要怎么定位解决 + +每次点击按钮内存都会增加一些,并且主动触发gc也不见回收,napi开发过程中的内存泄漏问题要怎么定位解决? + +- 解决建议: +先了解一下napi生命周期相关材料: +[使用Node-API接口进行生命周期相关开发](use-napi-life-cycle.md) +使用napi导致内存泄漏的常见原因: +1. napi_value不在handle_scope管控中,导致native持有的js对象不释放,尤其常见在直接使用uv_queue_work中。解决方案为添加 napi_open_handle_scope 和 napi_close_handle_scope 接口。 + +[易错API的使用规范](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-coding-standard-api#section1219614634615) + +此类泄漏可通过snapshot分析定位原因,泄漏的js对象distance为1,即不知道被谁持有,这种情况下一般就是被native(napi_value是个指针,指向native持有者)持有了,且napi_value不在handle_scope范围内。 +2. 使用了napi_create_reference为js对象创建了强引用(initial_refcount参数大于0),并且一直未delete,导致js对象一直被持有,无法被回收。napi_create_reference接口会new一个c++对象,因此这种泄漏通常表现为ArkTS与C++层的双重泄漏。可以使用Allocation工具抓c++泄漏栈,参考是否有napi_create_reference相关栈帧。 + +[基础内存分析:Allocation分析](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-insight-session-allocations) + +3. 被其他存活js对象持有,这时候snapshot查看泄漏对象被谁持有。 + +## napi_threadsafe_function内存泄漏应该如何处理 + +`napi_threadsafe_function`(下文简称tsfn)在使用时,常常会调用 `napi_acquire_threadsafe_function` 来更改tsfn的引用计数,确保tsfn不会意外被释放。但在使用完成后,应该及时使用 `napi_tsfn_release` 模式调用 `napi_release_threadsafe_function` 方法,以确保在所有调用回调都执行完成后,其引用计数能回归到调用 `napi_acquire_threadsafe_function` 方法之前的水平。当其引用计数归为0时,tsfn才能正确的被释放。 + +当在env即将退出,但tsfn的引用计数未被归零时,应该使用 `napi_tsfn_abort` 模式调用 `napi_release_threadsafe_function` 方法,确保在env释放后不再对tsfn进行持有及使用。在env退出后,继续持有tsfn进行使用,是一种未定义的行为,可能会触发崩溃。 + +如下代码将展示通过注册 `env_cleanup` 钩子函数的方式,以确保在env退出后不再继续持有tsfn。 + +```cpp +//napi_init.cpp +#include // hilog, 输出日志, 需链接 libhilog_ndk.z.so +#include // 创建线程 +#include // 线程休眠 + +// 定义输出日志的标签和域 +#undef LOG_DOMAIN +#undef LOG_TAG +#define LOG_DOMAIN 0x2342 +#define LOG_TAG "MY_TSFN_DEMO" + +/* + 为构建一个 env 生命周期短于 native 生命周期的场景, + 本示例需要使用worker, taskpool 或 napi_create_ark_runtime 等方法, + 创建非主线程的ArkTS运行环境,并人为的提前结束掉该线程 +*/ + + +// 定义一个数据结构,模拟存储tsfn的场景 +class MyTsfnContext { +public: +// 因使用了napi方法, MyTsfnContext 应当只在js线程被构造 +MyTsfnContext(napi_env env, napi_value workName) { + // 注册env销毁钩子函数 + napi_add_env_cleanup_hook(env, Cleanup, this); + // 创建线程安全函数 + if (napi_create_threadsafe_function(env, nullptr, nullptr, workName, 1, 1, this, + TsfnFinalize, this, TsfnCallJs, &tsfn_) != napi_ok) { + OH_LOG_INFO(LOG_APP, "tsfn is created failed"); + return; + }; +}; + +~MyTsfnContext() { OH_LOG_INFO(LOG_APP, "MyTsfnContext is deconstructed"); }; + +napi_threadsafe_function GetTsfn() { + std::unique_lock lock(mutex_); + return tsfn_; +} + +bool Acquire() { + if (GetTsfn() == nullptr) { + return false; + }; + return (napi_acquire_threadsafe_function(GetTsfn()) == napi_ok); +}; + +bool Release() { + if (GetTsfn() == nullptr) { + return false; + }; + return (napi_release_threadsafe_function(GetTsfn(), napi_tsfn_release) == napi_ok); +}; + +bool Call(void *data) { + if (GetTsfn() == nullptr) { + return false; + }; + return (napi_call_threadsafe_function(GetTsfn(), data, napi_tsfn_blocking) == napi_ok); +}; + +private: +// 保护多线程读写tsfn的准确性 +std::mutex mutex_; +napi_threadsafe_function tsfn_ = nullptr; + +// napi_add_env_cleanup_hook 回调 +static void Cleanup(void *data) { + MyTsfnContext *that = reinterpret_cast(data); + napi_threadsafe_function tsfn = that->GetTsfn(); + std::unique_lock lock(that->mutex_); + that->tsfn_ = nullptr; + lock.unlock(); + OH_LOG_WARN(LOG_APP, "cleanup is called"); + napi_release_threadsafe_function(tsfn, napi_tsfn_abort); +}; + +// tsfn 释放时的回调 +static void TsfnFinalize(napi_env env, void *data, void *hint) { + MyTsfnContext *ctx = reinterpret_cast(data); + OH_LOG_INFO(LOG_APP, "tsfn is released"); + napi_remove_env_cleanup_hook(env, MyTsfnContext::Cleanup, ctx); + // cleanup 提前释放线程安全函数, 为避免UAF, 将释放工作交给调用方 + if (ctx->GetTsfn() != nullptr) { + OH_LOG_INFO(LOG_APP, "ctx is released"); + delete ctx; + } +}; + +// tsfn 发送到 ArkTS 线程执行的回调 +static void TsfnCallJs(napi_env env, napi_value func, void *context, void *data) { + MyTsfnContext *ctx = reinterpret_cast(context); + char *str = reinterpret_cast(data); + OH_LOG_INFO(LOG_APP, "tsfn is called, data is: \"%{public}s\"", str); + // 业务逻辑已省略 +}; +}; + +// 该方法需注册到模块Index.d.ts, 注册名为 myTsfnDemo, 接口描述如下 +// export const myTsfnDemo: () => void; +napi_value MyTsfnDemo(napi_env env, napi_callback_info info) { + OH_LOG_ERROR(LOG_APP, "MyTsfnDemo is called"); + napi_value workName = nullptr; + napi_create_string_utf8(env, "MyTsfnWork", NAPI_AUTO_LENGTH, &workName); + MyTsfnContext *myContext = new MyTsfnContext(env, workName); + if (myContext->GetTsfn() == nullptr) { + OH_LOG_ERROR(LOG_APP, "failed to create tsfn"); + delete myContext; + return nullptr; + }; + char *data0 = new char[]{"Im call in ArkTS Thread"}; + if (!myContext->Call(data0)) { + OH_LOG_INFO(LOG_APP, "call tsfn failed"); + }; + + // 创建一个线程,模拟异步场景 + std::thread( + [](MyTsfnContext *myCtx) { + if (!myCtx->Acquire()) { + OH_LOG_ERROR(LOG_APP, "acquire tsfn failed"); + return; + }; + char *data1 = new char[]{"Im call in std::thread"}; + // 非必要操作, 仅用于异步流程tsfn仍有效 + if (!myCtx->Call(data1)) { + OH_LOG_ERROR(LOG_APP, "call tsfn failed"); + }; + // 休眠 5s, 模拟耗时场景, env退出后, 异步任务仍未执行完成 + sleep(5); + // 此时异步任务已执行完成, 但tsfn已被释放并置为 nullptr + char *data2 = new char[]{"Im call after work"}; + if (!myCtx->Call(data2) && !myCtx->Release()) { + OH_LOG_ERROR(LOG_APP, "call and release tsfn failed"); + delete myCtx; + } + }, + myContext) + .detach(); + return nullptr; +}; +``` + +以下内容为主线程逻辑,主要用作创建worker线程和通知worker执行任务 + +```ts +// 主线程 Index.ets +import worker, { MessageEvents } from '@ohos.worker'; + +const mWorker = new worker.ThreadWorker('../workers/Worker'); +mWorker.onmessage = (e: MessageEvents) => { + const action: string | undefined = e.data?.action; + if (action === 'kill') { + mWorker.terminate(); + } +} + +// 触发方式的注册已省略 +mWorker.postMessage({action: 'tsfn-demo'}); + +``` + +以下内容为Worker线程逻辑,主要用以触发Native任务 + +```ts +// worker.ets +import worker, { ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@ohos.worker'; +import napiModule from 'libentry.so'; // libentry.so: napi 库的模块名称 + +const workerPort: ThreadWorkerGlobalScope = worker.workerPort; + +workerPort.onmessage = (e: MessageEvents) => { + const action: string | undefined = e.data?.action; + if (action === 'tsfn-demo') { + // 触发 c++ 层的 tsfn demo + napiModule.myTsfnDemo(); + // 通知主线程结束 worker + workerPort.postMessage({action: 'kill'}); + }; +} +``` \ No newline at end of file diff --git a/zh-cn/application-dev/napi/napi-faq-about-stability.md b/zh-cn/application-dev/napi/napi-faq-about-stability.md new file mode 100644 index 00000000000..7f41aaf9785 --- /dev/null +++ b/zh-cn/application-dev/napi/napi-faq-about-stability.md @@ -0,0 +1,129 @@ +# 稳定性相关问题汇总 + +## 应用运行过程中出现高概率闪退怎么进行定位解决 + +- 具体问题:在使用鸿蒙Node-Api(napi)开发过程中,应用运行过程中出现高概率闪退,出现cppcrash栈,栈顶为系统库libark_jsruntime.so,崩溃栈前几帧也有libace_napi.z.so,怎么进行定位解决? +复现概率高,每次崩溃栈略有区别,但是共性都是:崩溃栈顶是系统库的libark_jsruntime.so或者libace_napi.z.so +- 崩溃信息如下: +```sh +Reason:Signal:SIGSEGV(SEGV_MAPERR)@0x00000136 probably caus +Fault thread info: +Tid:15894, Name:e.myapplication +#00 pc 002b8dd4 /system/lib/platformsdk/libark_jsruntime.so +#01 pc 0024d3e1 /system/lib/platformsdk/libark_jsruntime.so +#02 pc 0024d0d9 /system/lib/platformsdk/libark_jsruntime.so +#03 pc 002eac5d /system/lib/platformsdk/libark_jsruntime.so +#04 pc 00428d0f /system/lib/platformsdk/libark_jsruntime.so +``` + +- 定位问题: +使用napi时如果出现高概率闪退,崩溃栈顶在系统库libark_jsruntime.so,一般是开发者napi接口使用不当导致。 +- 以下定位问题的思路,可作为参考: +1. 排查是否存在多线程安全问题(概率较大) +IDE中提供了相关开关,开启开关后,重新编译打包并运行,看看崩溃栈是不是符合下面这个文档的描述,如果是,那就是在使用napi时,存在多线程安全问题。 +[常见多线程安全问题](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-ark-runtime-detection#section19357830121120) +IDE开关: +![IDE多线程开关](figures/zh_cn_image_20-25-06-40-15-09.png) +2. 使用napi接口时入参非法导致。 +- 这种情况一般是崩溃栈上的so会很浅,so调用了某个具体的napi接口,比如调用了napi_call_function之类的接口,然后napi又调到了libark_jsruntime的so,然后直接崩溃在libark_jsruntime里面。 +示例栈结构如下。 +```sh +#01 /system/lib/platformsdk/libark_jsruntime.so +#02 /system/lib/platformsdk/libark_jsruntime.so +#03 /system/lib/platformsdk/libace_napi.z.so(napi_set_named_property+170) -- napi的so,该位置显示具体调用报错的接口 +#04 /data/storage/el1/bundle/libs/arm/libentry.so -- 你的so +``` +- 如果是入参问题,一般so在崩溃栈上的位置比较浅(不会跑到#10这种离栈顶很远的位置),不过也可以按照这个思路进行排查一下。 +- 排查思路参考: +a. 排查有没有napi_value未初始化,还没赋值成功,直接作为非法入参传递给接口了 +b. 排查有没有在这个易错API列表里面找到相应的篇章--[方舟运行时API](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-coding-standard-api#section1219614634615) + +## 线程池中并发调用ArkTS方法如何处理线程安全问题 + +- 现有个场景,ArkTS中有个类方法,对这个方法创建了napi_ref引用,现想在c++线程池中并发的调用ArkTS方法,请问 +1. 可以在c++创建的线程池中调用napi_ref缓存的ArkTS类方法吗? +只能在c++线程中将ArkTS任务抛回ts线程,这时候并不是同步调用,而是一个抛任务的动作。 +需要注意的是,这个ArkTS方法真正的执行动作只能在ts线程中完成,即,只能方法运行在创建的ts线程上。 +2. 回调到ArkTS要怎么确保线程安全? +上面提到,c++线程都是抛任务到ts线程,进而执行ts方法。关于线程安全,可参考napi线程安全文档,相关能力是有的。 +[使用Node-API接口进行线程安全开发](use-napi-thread-safety.md) +另外,开发过程中也可以打开多线程安全检查开关,这个开关可以拦截多线程安全问题。 +[方舟多线程检测](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-ark-runtime-detection#section75786272088) + +## napi_value内容产生变化 + +- 具体描述:在一个程序初始化的时候,保存了env和一个method(napi_value),这个method在刚刚创建的时候有进行check,napi_typeof的结果是napi_function,符合预期。程序运行一段时间后,使用保存的env和method再去调用,发现method check不过了,此时不是一个napi_function了,保存与使用时均处于同一主线程,要如何解决? + +排查建议: +1. 确认一下,是否napi_value出了scope还在使用,导致use-after-scope问题。 +可参考文档: +[方舟运行时API](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-coding-standard-api#section1219614634615) +2. 保存时建议使用napi_ref进行保存,而不是直接保存napi_value。 + +## 是否存在获取最新napi_env的方法 + +- 具体描述:有个场景,Native 层在较深的调用层级中需调用 ArkTS 方法,无法逐层传递 napi_env,直接缓存的话会有crash,崩溃栈如下: +```sh +#00 /system/lib/platformsdk/libark_jsruntime.so(panda::JSValueRef::IsFunction) +#01 /system/lib/platformsdk/libace_napi.z.so(napi_call_function) +#02 /data/storage/el1/bundle/libs/arm/libentry.so +... +``` +- 参考方案: +1. 关于保存napi_env: +napi没有提供直接获取napi_env的能力,只能通过传递一层一层传下来。一般不推荐保存napi_env,有两个原因: +其一,napi_env退出时候如果没有被使用方感知到,很容易出现use-after-free问题; +其二,napi_env和js线程时强绑定的,如果napi_env放在其他js线程使用,就会有多线程安全问题。 +可参考文档: +[napi_env禁止缓存的原因是什么](https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-ndk-73) + + +2. 该问题重点在于: +如果要强行保存env,就一定要感知env是否退出,用napi_add_env_cleanup_hook的回调去感知。同时在开发过程中把多线程检测开关打开,避免出现多线程安全问题。 +可参考多线程检测文档: +[常见多线程安全问题](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-ark-runtime-detection#section19357830121120) + +3. 崩溃本身,该崩溃可能发生在调用napi_call_function时,入参 func 有问题,即非法入参,开发者可排查napi_value是否被缓存。这种情况可能是napi_value被缓存之后,napi_value不在作用域导致失效。 +如果有类似逻辑,需使用napi_ref进行存储,napi_ref可以延长生命周期。 + +- 可参考文档: +[napi_create_reference、napi_delete_reference](use-napi-life-cycle.md) + +[方舟运行时API](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-coding-standard-api#section1219614634615) + +## napi_add_env_cleanup_hook调用报错该如何处理 + +-具体问题:napi_add_env_cleanup_hook/napi_remove_env_cleanup_hook调用报错,该如何处理? +`napi_add_env_cleanup_hook`/`napi_remove_env_cleanup_hook`调用报错,有以下几个常见原因和对应的特征日志,均为接口使用不当导致。 +1. 在`env`所在的js线程外使用上述两个接口,导致多线程安全问题。特征报错日志`current napi interface cannot run in multi-thread`。 +2. 调用`napi_add_env_cleanup_hook`时,重复使用同一个`args`注册不同的回调函数,导致后续注册失败问题。该接口第三个入参`args`是作为接口内部`map`的`key`值,当重复注册同一个`args`的回调时,后续注册动作将会失败,仅第一次注册才会成功。注册失败可能导致后续业务功能异常或崩溃。特征报错日志`AddCleanupHook Failed`。 +3. 调用`napi_remove_env_cleanup_hook`时,尝试通过一个不存在(或已被删除)的`args`删除回调函数,该接口调用失败,出现特征报错日志`RemoveCleanupHook Failed`。 + +常见错误场景示例如下: + +```c++ +void AddEnvCleanupHook(napi_env env) +{ + napi_add_env_cleanup_hook(env, [](void* args) -> void { + // cleanup function回调 + }, env); // env是个通用的数据,即使此处没有重复注册,可能会被其他地方所提前注册,导致该处注册失败。 +} + +static napi_value Test(napi_env env, napi_callback_info info) +{ + //第一次注册 + AddEnvCleanupHook(env); + //第二次重复注册 + AddEnvCleanupHook(env); + return nullptr; +} +``` + +- 修复建议: +1. 对于多线程安全问题,需确保调用接口的线程在`env`所在的js线程上。 +2. 对于注册失败的问题,需由使用者明确待注册的函数。需要保证`key`值(也就是`napi_add_env_cleanup_hook`的第三个入参)是唯一的即可。 +3. 对于删除失败的问题,需要使用者确保`args`之前被注册过且未被删除。 + +相关参考资料链接: +[使用Node-API接口注册和使用环境清理钩子](use-napi-about-cleanuphook.md) +[方舟运行时API](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-coding-standard-api#section1219614634615) diff --git a/zh-cn/application-dev/napi/use-napi-faqs.md b/zh-cn/application-dev/napi/use-napi-faqs.md index e95b141a781..de6601ac465 100644 --- a/zh-cn/application-dev/napi/use-napi-faqs.md +++ b/zh-cn/application-dev/napi/use-napi-faqs.md @@ -1,395 +1,33 @@ # Node-API常见问题 -## ArkTS/JS侧import xxx from libxxx.so后,使用xxx报错显示undefined/not callable或明确的Error message +## 一.稳定性 +1. [应用运行过程中出现高概率闪退,出现cppcrash栈,栈顶为系统库libark_jsruntime.so,崩溃栈前几帧也有libace_napi.z.so,怎么进行定位解决](napi-faq-about-stability.md#应用运行过程中出现高概率闪退怎么进行定位解决) +2. [c++线程池中并发调用ArkTS方法(c++多线程调用ArkTS方法),如何处理线程安全问题?](napi-faq-about-stability.md#线程池中并发调用ArkTS方法如何处理线程安全问题) +3. [【napi_value非预期】napi_value内容产生变化,napi_value创建时类型是napi_function,保存一段时间后napi_value类型发生变化](napi-faq-about-stability.md#napi_value内容产生变化) +4. [是否存在获取最新napi_env的方法?](napi-faq-about-stability.md#是否存在获取最新napi_env的方法) +5. [napi_add_env_cleanup_hook/napi_remove_env_cleanup_hook调用报错,该如何处理?](napi-faq-about-stability.md#napi_add_env_cleanup_hook调用报错该如何处理) +## 二.内存泄漏 +1. [napi_create_reference可以创建对js对象的引用,保持js对象不释放,正常来说使用完需要使用napi_delete_reference进行释放,但怕漏delete导致js对象内存泄漏,请问当前是否有机制来检查/测试是否有泄漏的napi_reference?](napi-faq-about-memory-leak.md#请问当前是否有机制来检查是否有泄漏的napi_ref) +2. [napi开发过程中,遇见内存泄漏问题,要怎么定位解决?](napi-faq-about-memory-leak.md#napi开发过程中遇见内存泄漏问题要怎么定位解决) +3. [参数泄漏问题参考napi_open_handle_scope、napi_close_handle_scope](use-napi-life-cycle.md) +4. [napi_threadsafe_function内存泄漏,应该如何处理?](napi-faq-about-memory-leak.md#napi_threadsafe_function内存泄漏应该如何处理) +## 三.常见基本功能问题 +1. [模块加载失败,Error message: is not callable NativeModule调用报错?](napi-faq-about-common-basic.md) +2. [是否有保序的线程通信推荐写法?](napi-faq-about-common-basic.md#在大量需要调用ArkTS方法进行通信的场景如何保证异步任务的有序性) +3. [是否存在便捷的NAPI回调arkts的方式?](napi-faq-about-common-basic.md#是否存在便捷的回调ArkTS的方式) +4. [如何在C++调用从ArkTS传递过来的function?](https://developer.huawei.com/consumer/cn/doc/harmonyos-faqs/faqs-ndk-26) +5. [如何在遵循 N-API 单一返回值约束的前提下,安全、高效地将多个返回值(包括结构化数据和指针信息)传递给 ArkTS 运行时环境,并确保数据类型的正确映射与内存管理的安全性?](napi-faq-about-common-basic.md#如何确保数据类型的正确映射与内存管理的安全性) +6. [napi调用三方so](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-dynamic-link-library) +7. [napi_get_uv_event_loop接口错误码说明](napi-faq-about-common-basic.md#napi_get_uv_event_loop接口错误码说明) +8. [【NAPI】native层调用ts层对象方法,必须传入一个function给native层吗?](napi-faq-about-common-basic.md#Native层调用ts层对象方法必须传入一个function给Native层吗) +9. [在c++通过pthread或std::thread创建的线程,是否能调用ets的方法并获取到结果?](napi-faq-about-common-basic.md#是否能调用ets的方法并获取到结果) +10. [当前napi的napi_get_value_string_utf8每次调用的时候都要进行拷贝,是否有nocopy、不拷贝的napi_get_value_string_utf8接口或者能力? ](napi-faq-about-common-basic.md#是否有不拷贝的napi_get_value_string_utf8接口或者能力) +11. [鸿蒙多线程下napi env的使用注意事项是什么?是否存在napi_env切换导致的异常问题?是否必须在主线程?](napi-faq-about-common-basic.md#鸿蒙多线程下napi_env的使用注意事项) +12. [请问napi_call_threadsafe_function执行顺序是怎样的?](napi-faq-about-common-basic.md#napi_call_threadsafe_function执行顺序不符合预期) +13. [ArkTS/JS侧import xxx from libxxx.so后,使用xxx报错显示undefined/not callable或明确的Error message](napi-faq-about-common-basic.md#ArkTS侧报错显示undefined) +14. [接口执行结果非预期,日志显示occur exception need return](napi-faq-about-common-basic.md#接口执行结果非预期) +15. [napi_value和napi_ref的生命周期有何区别](napi-faq-about-common-basic.md#napi_value和napi_ref的生命周期有何区别) +16. [Node-API接口返回值不是napi_ok时,如何排查定位](napi-faq-about-common-basic.md#Node-API接口返回值不是napi_ok时如何排查定位) +17. [napi_wrap如何保证被wrap的对象按期望顺序析构?](napi-faq-about-common-basic.md#napi_wrap如何保证被wrap的对象按期望顺序析构) +18. -1. 排查.cpp文件在注册模块时的模块名称与so的名称匹配一致。 - 如模块名为entry,则so的名字为libentry.so,napi_module中nm_modname字段应为entry,大小写与模块名保持一致。 - -2. 排查so是否加载成功。 - 应用启动时过滤模块加载相关日志,重点搜索"dlopen"关键字,确认是否有相关报错信息;常见加载失败原因有权限不足、so文件不存在以及so已拉入黑名单等,可根据以下关键错误日志确认问题。其中,多线程场景(worker、taskpool等)下优先检查模块实现中nm_modname是否与模块名一致,区分大小写。 - -3. 排查依赖的so是否加载成功。 - 确定所依赖的其它so是否打包到应用中以及是否有权限打开。常见加载失败原因有权限不足、so文件不存在等,可根据以下关键错误日志确认问题。 - -4. 排查模块导入方式与so路径是否对应。 - 若JS侧导入模块的形式为: import xxx from '\@ohos.yyy.zzz',则该so将在/system/lib/module/yyy中找libzzz.z.so或libzzz_napi.z.so,若so不存在或名称无法对应,则报错日志中会出现dlopen相关日志。 - - 注意,32位系统路径为/system/lib,64位系统路径为/system/lib64。 - -| **已知关键错误日志** | **修改建议** | -| -------- | -------- | -| module $SO is not allowed to load in restricted runtime. | $SO表示模块名。该模块不在受限worker线程的so加载白名单,不允许加载,建议用户删除该模块。 | -| module $SO is in blocklist, loading prohibited. | $SO表示模块名。受卡片或者Extension管控,该模块在黑名单内,不允许加载,建议用户删除该模块。 | -| load module failed. $ERRMSG. | 动态库加载失败。$ERRMSG表示加载失败原因,一般常见原因是so文件不存在、依赖的so文件不存在或者符号未定义,需根据加载失败原因具体分析。 | -| try to load abc file from $FILEPATH failed. | 通常加载动态库和abc文件为二选一:如果是要加载动态库并且加载失败,该告警可以忽略;如果是要加载abc文件,则该错误打印的原因是abc文件不存在,$FILEPATH表示模块路径。 | - -5. 如果有明确的Error message,可以通过Error message判断当前问题。 - -| **Error message** | **修改建议** | -| -------- | -------- | -| First attempt: $ERRMSG. | 首先加载后缀不拼接'_napi'的模块名为'xxx'的so,如果加载失败会有该错误信息,$ERRMSG表示具体加载时的错误信息。 | -| Second attempt: $ERRMSG. | 第二次加载后缀拼接'_napi'的模块名为'xxx_napi'的so,如果加载失败会有该错误信息,$ERRMSG表示具体加载时的错误信息。 | -| try to load abc file from xxx failed. | 第三次加载名字为'xxx'的abc文件,如果加载失败会有该错误信息。 | -| module xxx is not allowed to load in restricted runtime. | 该模块不允许在受限运行时中使用,xxx表示模块名,建议用户删除该模块。 | -| module xxx is in blocklist, loading prohibited. | 该模块不允许在当前extension下使用,xxx表示模块名,建议用户删除该模块。 | - -## 接口执行结果非预期,日志显示occur exception need return - -部分Node-API接口在调用结束前会进行检查,检查虚拟机中是否存在JS异常。如果存在异常,则会打印出occur exception need return日志,并打印出检查点所在的行号,以及对应的Node-API接口名称。 - -解决此类问题有以下两种思路: - -- 若该异常开发者不关心,可以选择直接清除。 - 可直接使用napi接口napi_get_and_clear_last_exception,清理异常。调用时机:在打印occur exception need return日志的接口之前调用。 - -- 将该异常继续向上抛到ArkTS层,在ArkTS层进行捕获。 - 发生异常时,可以选择走异常分支, 确保不再走多余的Native逻辑 ,直接返回到ArkTS层。 - -## napi_value和napi_ref的生命周期有何区别 - -- native_value由HandleScope管理,一般开发者不需要自己加HandleScope(uv_queue_work的complete callback除外)。 - -- napi_ref由开发者自己管理,需要手动delete。 - -## Node-API接口返回值不是napi_ok时,如何排查定位 - -Node-API接口正常执行后,会返回一个napi_ok的状态枚举值,若napi接口返回值不为napi_ok,可从以下几个方面进行排查。 - -- Node-API接口执行前一般会进行入参校验,首先进行的是判空校验。在代码中体现为: - - ```cpp - CHECK_ENV: env判空校验 - CHECK_ARG:其它入参判空校验 - ``` - -- 某些Node-API接口还有入参类型校验。比如napi_get_value_double接口是获取JS number对应的C double值,首先就要保证的是:JS value类型为number,因此可以看到相关校验。 - - ```cpp - RETURN_STATUS_IF_FALSE(env, nativeValue->TypeOf() == NATIVE_NUMBER, napi_number_expected); - ``` - -- 还有一些接口会对其执行结果进行校验。比如napi_call_function这个接口,其功能是执行一个JS function,当JS function中出现异常时,Node-API将会返回napi_pending_exception的状态值。 - - ```cpp - auto resultValue = engine->CallFunction(nativeRecv, nativeFunc, nativeArgv, argc); - RETURN_STATUS_IF_FALSE(env, resultValue != nullptr, napi_pending_exception) - ``` - -- 还有一些状态值需要根据相应Node-API接口具体分析:确认具体的状态值,分析这个状态值在什么情况下会返回,再排查具体出错原因。 - -## napi_threadsafe_function内存泄漏,应该如何处理 - -`napi_threadsafe_function`(下文简称tsfn)在使用时,常常会调用 `napi_acquire_threadsafe_function` 来更改tsfn的引用计数,确保tsfn不会意外被释放。但在使用完成后,应该及时使用 `napi_tsfn_release` 模式调用 `napi_release_threadsafe_function` 方法,以确保在所有调用回调都执行完成后,其引用计数能回归到调用 `napi_acquire_threadsafe_function` 方法之前的水平。当其引用计数归为0时,tsfn才能正确的被释放。 - -当在env即将退出,但tsfn的引用计数未被归零时,应该使用 `napi_tsfn_abort` 模式调用 `napi_release_threadsafe_function` 方法,确保在env释放后不再对tsfn进行持有及使用。在env退出后,继续持有tsfn进行使用,是一种未定义的行为,可能会触发崩溃。 - -如下代码将展示通过注册 `env_cleanup` 钩子函数的方式,以确保在env退出后不再继续持有tsfn。 - -```cpp -//napi_init.cpp -#include // hilog, 输出日志, 需链接 libhilog_ndk.z.so -#include // 创建线程 -#include // 线程休眠 - -// 定义输出日志的标签和域 -#undef LOG_DOMAIN -#undef LOG_TAG -#define LOG_DOMAIN 0x2342 -#define LOG_TAG "MY_TSFN_DEMO" - -/* - 为构造一个env生命周期小于native生命周期的场景, - 本文需要使用worker, taskpool 或 napi_create_ark_runtime 等方法, - 创建非主线程的ArkTS运行环境,并人为的提前结束掉该线程 -*/ - - -// 定义一个数据结构,模拟存储tsfn的场景 -class MyTsfnContext { -public: -// 因使用了napi方法, MyTsfnContext 应当只在js线程被构造 -MyTsfnContext(napi_env env, napi_value workName) { - // 注册env销毁钩子函数 - napi_add_env_cleanup_hook(env, Cleanup, this); - // 创建线程安全函数 - if (napi_create_threadsafe_function(env, nullptr, nullptr, workName, 1, 1, this, - TsfnFinalize, this, TsfnCallJs, &tsfn_) != napi_ok) { - OH_LOG_INFO(LOG_APP, "tsfn is created failed"); - return; - }; -}; - -~MyTsfnContext() { OH_LOG_INFO(LOG_APP, "MyTsfnContext is deconstructed"); }; - -napi_threadsafe_function GetTsfn() { - std::unique_lock lock(mutex_); - return tsfn_; -} - -bool Acquire() { - if (GetTsfn() == nullptr) { - return false; - }; - return (napi_acquire_threadsafe_function(GetTsfn()) == napi_ok); -}; - -bool Release() { - if (GetTsfn() == nullptr) { - return false; - }; - return (napi_release_threadsafe_function(GetTsfn(), napi_tsfn_release) == napi_ok); -}; - -bool Call(void *data) { - if (GetTsfn() == nullptr) { - return false; - }; - return (napi_call_threadsafe_function(GetTsfn(), data, napi_tsfn_blocking) == napi_ok); -}; - -private: -// 保护多线程读写tsfn的准确性 -std::mutex mutex_; -napi_threadsafe_function tsfn_ = nullptr; - -// napi_add_env_cleanup_hook 回调 -static void Cleanup(void *data) { - MyTsfnContext *that = reinterpret_cast(data); - napi_threadsafe_function tsfn = that->GetTsfn(); - std::unique_lock lock(that->mutex_); - that->tsfn_ = nullptr; - lock.unlock(); - OH_LOG_WARN(LOG_APP, "cleanup is called"); - napi_release_threadsafe_function(tsfn, napi_tsfn_abort); -}; - -// tsfn 释放时的回调 -static void TsfnFinalize(napi_env env, void *data, void *hint) { - MyTsfnContext *ctx = reinterpret_cast(data); - OH_LOG_INFO(LOG_APP, "tsfn is released"); - napi_remove_env_cleanup_hook(env, MyTsfnContext::Cleanup, ctx); - // cleanup 提前释放线程安全函数, 为避免UAF, 将释放工作交给调用方 - if (ctx->GetTsfn() != nullptr) { - OH_LOG_INFO(LOG_APP, "ctx is released"); - delete ctx; - } -}; - -// tsfn 发送到 js 线程执行的回调 -static void TsfnCallJs(napi_env env, napi_value func, void *context, void *data) { - MyTsfnContext *ctx = reinterpret_cast(context); - char *str = reinterpret_cast(data); - OH_LOG_INFO(LOG_APP, "tsfn is called, data is: \"%{public}s\"", str); - // 业务逻辑已省略 -}; -}; - -// 该方法需注册到模块Index.d.ts, 注册名为 myTsfnDemo, 接口描述如下 -// export const myTsfnDemo: () => void; -napi_value MyTsfnDemo(napi_env env, napi_callback_info info) { - OH_LOG_ERROR(LOG_APP, "MyTsfnDemo is called"); - napi_value workName = nullptr; - napi_create_string_utf8(env, "MyTsfnWork", NAPI_AUTO_LENGTH, &workName); - MyTsfnContext *myContext = new MyTsfnContext(env, workName); - if (myContext->GetTsfn() == nullptr) { - OH_LOG_ERROR(LOG_APP, "failed to create tsfn"); - delete myContext; - return nullptr; - }; - char *data0 = new char[]{"Im call in ArkTS Thread"}; - if (!myContext->Call(data0)) { - OH_LOG_INFO(LOG_APP, "call tsfn failed"); - }; - - // 创建一个线程,模拟异步场景 - std::thread( - [](MyTsfnContext *myCtx) { - if (!myCtx->Acquire()) { - OH_LOG_ERROR(LOG_APP, "acquire tsfn failed"); - return; - }; - char *data1 = new char[]{"Im call in std::thread"}; - // 非必要操作, 仅用于异步流程tsfn仍有效 - if (!myCtx->Call(data1)) { - OH_LOG_ERROR(LOG_APP, "call tsfn failed"); - }; - // 休眠 5s, 模拟耗时场景, env退出后, 异步任务仍未执行完成 - sleep(5); - // 此时异步任务已执行完成, 但tsfn已被释放并置为 nullptr - char *data2 = new char[]{"Im call after work"}; - if (!myCtx->Call(data2) && !myCtx->Release()) { - OH_LOG_ERROR(LOG_APP, "call and release tsfn failed"); - delete myCtx; - } - }, - myContext) - .detach(); - return nullptr; -}; -``` - -以下内容为主线程逻辑,主要用作创建worker线程和通知worker执行任务 - -```ts -// 主线程 Index.ets -import worker, { MessageEvents } from '@ohos.worker'; - -const mWorker = new worker.ThreadWorker('../workers/Worker'); -mWorker.onmessage = (e: MessageEvents) => { - const action: string | undefined = e.data?.action; - if (action === 'kill') { - mWorker.terminate(); - } -} - -// 触发方式的注册已省略 -mWorker.postMessage({action: 'tsfn-demo'}); - -``` - -以下内容为Worker线程逻辑,主要用以触发Native任务 - -```ts -// worker.ets -import worker, { ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@ohos.worker'; -import napiModule from 'libentry.so'; // libentry.so: napi 库的模块名称 - -const workerPort: ThreadWorkerGlobalScope = worker.workerPort; - -workerPort.onmessage = (e: MessageEvents) => { - const action: string | undefined = e.data?.action; - if (action === 'tsfn-demo') { - // 触发 c++ 层的 tsfn demo - napiModule.myTsfnDemo(); - // 通知主线程结束 worker - workerPort.postMessage({action: 'kill'}); - }; -} -``` - -## napi_get_uv_event_loop接口错误码说明 - -在OpenHarmony中,对使用非法的napi_env作为`napi_get_uv_event_loop`入参的行为加入了额外的参数校验,这一行为将直接反映到该接口的返回值上。该接口返回值详情如下: - -1. 当env且(或)loop为nullptr时,返回`napi_invalid_arg`。 -2. 当env为有效的napi_env且loop为合法指针,返回`napi_ok`。 -3. 当env不是有效的napi_env(如已释放的env),返回`napi_generic_failure`。 - -常见错误场景示例如下: - -```c++ -napi_value NapiInvalidArg(napi_env env, napi_callback_info) -{ - napi_status status = napi_ok; - status = napi_get_uv_event_loop(env, nullptr); // loop为nullptr, napi_invalid_arg - if (status == napi_ok) { - // do something - } - - uv_loop_s* loop = nullptr; - status = napi_get_uv_event_loop(nullptr, &loop); // env为nullptr, napi_invalid_arg - if (status == napi_ok) { - // do something - } - - status = napi_get_uv_event_loop(nullptr, nullptr); // env, loop均为nullptr, napi_invalid_arg - if (status == napi_ok) { - // do something - } - - return nullptr; -} - -napi_value NapiGenericFailure(napi_env env, napi_callback_info) -{ - std::thread([]() { - napi_env env = nullptr; - napi_create_ark_runtime(&env); // 通常情况下,需要对该接口返回值进行判断 - // napi_destroy_ark_runtime 会将指针置空,拷贝一份用以构造问题场景 - napi_env copiedEnv = env; - napi_destroy_ark_runtime(&env); - uv_loop_s* loop = nullptr; - napi_status status = napi_get_uv_event_loop(copiedEnv, &loop); // env无效, napi_generic_failure - if (status == napi_ok) { - // do something - } - }).detach();; -} -``` - -## napi_add_env_cleanup_hook/napi_remove_env_cleanup_hook调用报错,该如何处理 -`napi_add_env_cleanup_hook`/`napi_remove_env_cleanup_hook`调用报错,有以下几个常见原因和对应的特征日志,均为接口使用不当导致。 -1. 在`env`所在的js线程外使用上述两个接口,导致多线程安全问题。特征报错日志`ecma_vm cannot run in multi-thread`。 -2. 调用`napi_add_env_cleanup_hook`时,重复使用同一个`args`注册不同的回调函数,导致后续注册失败问题。该接口第三个入参`args`是作为接口内部`map`的`key`值,当重复注册同一个`args`的回调时,后续注册动作将会失败,仅第一次注册才会成功。注册失败可能会引起后续业务上的功能/崩溃问题。特征报错日志`AddCleanupHook Failed`。 -3. 调用`napi_remove_env_cleanup_hook`时,尝试通过一个不存在(或已被删除)的`args`删除回调函数,该接口调用失败,出现特征报错日志`RemoveCleanupHook Failed`。 - -常见错误场景示例如下: - -```c++ -void AddEnvCleanupHook(napi_env env) -{ - napi_add_env_cleanup_hook(env, [](void* args) -> void { - // cleanup function回调 - }, env); // env是个通用的数据,即使此处没有重复注册,可能会被其他地方所提前注册,导致该处注册失败。 -} - -static napi_value Test(napi_env env, napi_callback_info info) -{ - //第一次注册 - AddEnvCleanupHook(env); - //第二次重复注册 - AddEnvCleanupHook(env); - return nullptr; -} -``` - -修复建议: -1. 对于多线程安全问题,需确保调用接口的线程在`env`所在的js线程上。 -2. 对于注册失败的问题,需要使用者评估想注册的函数到底是哪一个。需要保证`key`值(也就是`napi_add_env_cleanup_hook`的第三个入参)是唯一的即可。 -3. 对于删除失败的问题,需要使用者确保`args`之前被注册过且未被删除。 - -相关参考资料链接: -[使用Node-API接口注册和使用环境清理钩子](use-napi-about-cleanuphook.md) -[方舟运行时的NApi](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-coding-standard-api#section1219614634615) - -## `napi_wrap`如何保证被wrap的对象按期望顺序析构 -问题:在使用`napi_wrap`把两个 C++ 对象包装成两个 JavaScript 对象的场景中,由于这两个 C++ 对象存在依赖关系,要求其中一个c++对象必须在另一个c++对象之前析构。然而,JavaScript 垃圾回收(GC)的时机不确定,直接在`napi_wrap`的`finalize_cb`回调里销毁 C++ 对象,没办法保证析构顺序符合要求。该如何保证两个c++对象析构的前后顺序? - -参考方案: -先标记可释放状态,当A和B都为可释放状态时同时释放C++对象 -原理:将所有依赖对象的释放逻辑集中在最后一个被销毁的 JS 对象的 finalize_cb 中处理。 -实现步骤: -在 jsObjA 的 finalize_cb 中标记 cppObjA 为待销毁(不立即释放)。 -在 jsObjB 的 finalize_cb 中标记 cppObjB 为待销毁(不立即释放)。 -jsObjA 和 jsObjB 都为待销毁状态时,按顺序销毁A和B。 -示例代码: -```cpp -struct ObjectPair { - CppObjA* objA; - CppObjB* objB; - bool objADestroyedA = false; - bool objADestroyedB = false; -}; - -// jsObjA 的 finalize 回调 -void FinalizeA(napi_env env, void* data, void* hint) { - ObjectPair* pair = static_cast(data); - pair->objADestroyedA = true; - if (pair->objADestroyedA && pair->objADestroyedB) { - delete pair->objA; // 确保先销毁 A - delete pair->objB; // 再销毁 B - delete pair; // 释放包装结构 - } -} - -// jsObjB 的 finalize 回调 -void FinalizeB(napi_env env, void* data, void* hint) { - ObjectPair* pair = static_cast(data); - pair->objADestroyedB = true; - if (pair->objADestroyedA && pair->objADestroyedB) { - delete pair->objA; // 确保先销毁 A - delete pair->objB; // 再销毁 B - delete pair; // 释放包装结构 - } -} -``` diff --git a/zh-cn/application-dev/website.md b/zh-cn/application-dev/website.md index c524bad5ed2..72700374f9f 100644 --- a/zh-cn/application-dev/website.md +++ b/zh-cn/application-dev/website.md @@ -1835,7 +1835,11 @@ - [使用Node-API接口创建、切换和销毁上下文环境](napi/use-napi-about-context.md) - [使用Node-API接口产生的异常日志/崩溃分析](napi/use-napi-about-crash.md) - [使用Node-API调用返回值为promise的ArkTS方法](napi/use-napi-method-promise.md) - - [Node-API常见问题](napi/use-napi-faqs.md) + - Node-API常见问题汇总 + - [Node-API常见问题](napi/use-napi-faqs.md) + - [稳定性相关问题汇总](napi/napi-faq-about-stability.md) + - [内存泄漏相关问题汇总](napi/napi-faq-about-memory-leak.md) + - [常见基本功能问题汇总](napi/napi-faq-about-common-basic.md) - 使用JSVM-API实现JS与C/C++语言交互 - [JSVM-API简介](napi/jsvm-introduction.md) - [JSVM-API支持的数据类型和接口](napi/jsvm-data-types-interfaces.md) -- Gitee