篇博客我們將介紹Java中的一個關鍵字——native。
native 關鍵字在 JDK 源碼中很多類中都有,在 Object.java類中,其 getClass() 方法、hashCode()方法、clone() 方法等等都是用 native 關鍵字修飾的。
那么為什么要用 native 來修飾方法,這樣做有什么用?
在介紹 native 之前,我們先了解什么是 JNI。
一般情況下,我們完全可以使用 Java 語言編寫程序,但某些情況下,Java 可能會不滿足應用程序的需求,或者是不能更好的滿足需求,比如:
上面這三種需求,其實說到底就是如何用 Java 代碼調用不同語言編寫的代碼。那么 JNI 應運而生了。
從Java 1.1開始,Java Native Interface (JNI)標準就成為java平臺的一部分,它允許Java代碼和其他語言寫的代碼進行交互。JNI一開始是為了本地已編譯語言,尤其是C和C++而設計 的,但是它并不妨礙你使用其他語言,只要調用約定受支持就可以了。使用java與本地已編譯的代碼交互,通常會喪失平臺可移植性。但是,有些情況下這樣做是可以接受的,甚至是必須的,比如,使用一些舊的庫,與硬件、操作系統進行交互,或者為了提高程序的性能。JNI標準至少保證本地代碼能工作在任何Java 虛擬機實現下。
通過 JNI,我們就可以通過 Java 程序(代碼)調用到操作系統相關的技術實現的庫函數,從而與其他技術和系統交互,使用其他技術實現的系統的功能;同時其他技術和系統也可以通過 JNI 提供的相應原生接口開調用 Java 應用系統內部實現的功能。
在windows系統上,一般可執行的應用程序都是基于 native 的PE結構,windows上的 JVM也是基于native結構實現的。Java應用體系都是構建于 JVM 之上。
可能有人會問,Java不是跨平臺的嗎?如果用 JNI,那么程序不就將失去跨平臺的優點?確實是這樣的。
JNI 的缺點:
①、程序不再跨平臺。要想跨平臺,必須在不同的系統環境下重新編譯本地語言部分。
②、程序不再是絕對安全的,本地代碼的不當使用可能導致整個程序崩潰。一個通用規則是,你應該讓本地方法集中在少數幾個類當中。這樣就降低了JAVA和C之間的耦合性。
目前來講使用 JNI 的缺點相對于優點還是可以接受的,可能后面隨著 Java 的技術發展,我們不在需要 JNI,但是目前 JDK 還是一直提供對 JNI 標準的支持。
上面講解了什么是 JNI,那么我們接下來就寫個例子,如何用 Java 代碼調用本地的 C 程序。
步驟如下:
①、編寫帶有 native 聲明的方法的java類,生成.java文件;(注意這里出現了 native 聲明的方法關鍵字)
②、使用 javac 命令編譯所編寫的java類,生成.class文件;
③、使用 javah -jni java類名 生成擴展名為 h 的頭文件,也即生成.h文件;
④、使用C/C++(或者其他編程想語言)實現本地方法,創建.h文件的實現,也就是創建.cpp文件實現.h文件中的方法;
⑤、將C/C++編寫的文件生成動態連接庫,生成dll文件;
下面我們通過一個 HelloWorld 程序的調用來完成這幾個步驟。
注意:下面所有操作都是在所有操作都是在目錄:D:\JNI 下進行的。
一、編寫帶有native聲明的方法的java類
用 native 聲明的方法表示告知 JVM 調用,該方法在外部定義,也就是我們會用 C 語言去實現。
System.loadLibrary("helloJNI");加載動態庫,參數 helloJNI 是動態庫的名字。我們可以這樣理解:程序中的方法helloJNI() 在程序中沒有實現,但是我們下面要調用這個方法,怎么辦呢?我們就需要對這個方法進行初始化,所以用 static 代碼塊進行初始化。
這時候如果我們直接運行該程序,會報“A Java Exception has occurred”錯誤:
執行上述命令后,生成 HelloJNI.class 文件:
執行上述命令后,在 D:/JNI 目錄下多出了個 HelloJNI.h 文件:
如果不想安裝visual studio的,我們需要在 windows平臺安裝 gcc。
注意安裝版本的選擇,根據系統是32位還是64位來選擇。
安裝完成之后注意配置環境變量,在 cmd 中輸入 g++ -v,如果出現如下信息,則安裝配置完成:
接著輸入如下命令:
-m64表示生成dll庫是64位的。后面的路徑表示本機安裝的JDK路徑。生成之后多了一個helloJNI.dll 文件
最后運行 HelloJNI:輸出 Hello JNI! 大功告成。
通過上面介紹了那么多JNI的知識,終于到介紹本篇文章的主角——native 關鍵字了。相信大家看完上面的介紹,應該也是知道什么是 native 了吧。
native 用來修飾方法,用 native 聲明的方法表示告知 JVM 調用,該方法在外部定義,我們可以用任何語言去實現它。簡單地講,一個native Method就是一個 Java 調用非 Java 代碼的接口。
native 語法:
①、修飾方法的位置必須在返回類型之前,和其余的方法控制符前后關系不受限制。
②、不能用 abstract 修飾,也沒有方法體,也沒有左右大括號。
③、返回值可以是任意類型
我們在日常編程中看到native修飾的方法,只需要知道這個方法的作用是什么,至于別的就不用管了,操作系統會給我們實現。
如若轉載,請注明出處:開源字節 https://sourcebyte.vip/article/385.html
者|Lukas Gisder-Dubé
譯者|謝麗
本文將分三部分分析 JavaScript 中的錯誤,首先我們將了解錯誤的一般情況,之后,我們將關注后端(Node.js + Express.js),最后,我們將重點看下如何處理 React.js 中的錯誤。選擇這些框架,是因為它們是目前最流行的,但是,你應該也能夠將這些新發現應用到其他框架中吧!
繼上一篇文章 (https://link.medium.com/MO32x55aNR) 之后,我想談談錯誤。錯誤很好——我相信你以前聽過這個說法。乍一看,我們害怕錯誤,因為錯誤往往會涉及到在公共場合受到傷害或羞辱。通過犯錯誤,我們實際上學會了如何不去做某事,以及下次如何做得更好。
顯然,這是關于從現實生活的錯誤中學習。編程中的錯誤有點不同。它們為我們提供了很好的特征來改進我們的代碼,并告訴用戶什么地方出了問題(也可能是教他們如何修復它)。
GitHub(https://github.com/gisderdube/graceful-error-handling) 上提供了一個完整的樣例項目。
throw new Error('something went wrong')?會在 JavaScript 中創建一個錯誤實例,并停止腳本的執行,除非你對錯誤做了一些處理。當你作為 JavaScript 開發者開啟自己的職業生涯時,你自己很可能不會這樣做,但是,你已經從其他庫(或運行時)那里看到了,例如,類似“ReferenceError: fs 未定義”這樣的錯誤。
Error 對象
Error 對象有兩個內置屬性供我們使用。第一個是消息,作為參數傳遞給 Error 構造函數,例如 new Error(“這是錯誤消息”)。你可以通過 message 屬性訪問消息:
const myError=new Error(‘請改進代碼’) console.log(myError.message) // 請改進代碼
第二個是錯誤堆棧跟蹤,這個屬性非常重要。你可以通過 stack 屬性訪問它。錯誤堆棧將為你提供歷史記錄(調用堆棧),從中可以查看哪個文件導致了錯誤。堆棧的上部也包括消息,然后是實際的堆棧,從距離錯誤發生最近的點開始,一直到最外層“需要為錯誤負責”的文件:
Error: 請改進代碼 at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79) at Module._compile (internal/modules/cjs/loader.js:689:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Function.Module.runMain (internal/modules/cjs/loader.js:742:12) at startup (internal/bootstrap/node.js:266:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)
拋出和處理錯誤
現在,Error 實例本身不會導致任何結果,例如,new Error('...') 不會做任何事情。當錯誤被拋出時,就會變得更有趣。然后,如前所述,腳本將停止執行,除非你在流程中以某種方式對它進行了處理。記住,是手動拋出錯誤,還是由庫拋出錯誤,甚至由運行時本身(Node 或瀏覽器),都沒有關系。讓我們看看如何在不同的場景中處理這些錯誤。
try .... catch
這是最簡單但經常被遺忘的錯誤處理方法——多虧 async / await,它的使用現在又多了起來。它可以用來捕獲任何類型的同步錯誤,例如,如果我們不把 console.log(b) 放在一個 try … catch 塊中,腳本會停止執行。
… finally
有時候,不管是否有錯誤,代碼都需要執行。你可以使用第三個可選塊 finally。通常,這與在 try…catch 語句后面加一行代碼是一樣的,但它有時很有用。
異步性——回調
異步性,這是在使用 JavaScript 時必須考慮的一個主題。當你有一個異步函數,并且該函數內部發生錯誤時,你的腳本將繼續執行,因此,不會立即出現任何錯誤。當使用回調函數處理異步函數時(不推薦),你通常會在回調函數中收到兩個參數,如下所示:
如果有錯誤,err 參數就等同于那個錯誤。如果沒有,參數將是 undefined 或 null。要么在 if(err) 塊中返回某項內容,要么將其他指令封裝在 else 塊中,這一點很重要,否則你可能會得到另一個錯誤,例如,result 可能未定義,而你試圖訪問 result.data,類似這樣的情況。
異步性——Promises
處理異步性的更好方法是使用 Promises。在這一點上,除了代碼可讀性更強之外,我們還改進了錯誤處理。只要有一個 catch 塊,我們就不再需要太關注具體的錯誤捕獲。在鏈接 Promises 時,catch 塊捕獲會自 Promises 執行或上一個 catch 塊以來的所有錯誤。注意,沒有 catch 塊的 Promises 不會終止腳本,但會給你一條可讀性較差的消息,比如:
(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong (node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */
因此,務必要在 Promises 中加入 catch 塊。
回到 try … catch
隨著 JavaScript 引入 async / await,我們回到了最初的錯誤處理方法,借助 try … catch … finally,錯誤處理變得非常簡單。
因為這和我們處理“普通”同步錯誤的方法是一樣的,所以如果需要的話,更容易使用作用域更大的 catch 語句。
現在,我們已經有了處理錯誤的工具,讓我們看下,我們在實際情況下能用它們做什么。后端錯誤的產生和處理是應用程序至關重要的組成部分。對于錯誤處理,有不同的方法。我將向你展示一個自定義 Error 構造函數和錯誤代碼的方法,我們可以輕松地傳遞到前端或任何 API 消費者。構建后端的細節并不重要,基本思路不變。
我們將使用 Express.js 作為路由框架。讓我們考慮下最有效的錯誤處理結構。我們希望:
構建一個自定義 Error 構造函數
我們將使用已有的 Error 構造函數并擴展它。繼承在 JavaScript 中是一件危險的事情,但根據我的經驗,在這種情況下,它非常有用。我們為什么需要它?我們仍然希望堆棧跟蹤給我們一個很好的調試體驗。擴展 JavaScript 原生 Error 構造函數可以讓我們方便地獲得堆棧跟蹤。我們唯一要做的是添加代碼(我們稍后可以通過錯誤代碼訪問)和要傳遞給前端的狀態(http 狀態代碼)。
如何處理路由
在完成 Error 的自定義之后,我們需要設置路由結構。正如我指出的那樣,我們想要一個單點真錯誤處理,就是說,對于每一個路由,我們要有相同的錯誤處理行為。在默認情況下,由于路由都是封裝的,所以 Express 并不真正支持那種方式。
為了解決這個問題,我們可以實現一個路由處理程序,并把實際的路由邏輯定義為普通的函數。這樣,如果路由功能(或任何內部函數)拋出一個錯誤,它將返回到路由處理程序,然后可以傳給前端。當后端發生錯誤時,我們可以用以下格式傳遞一個響應給前端——比如一個 JSON API:
{ error: 'SOME_ERROR_CODE', description: 'Something bad happened. Please try again or contact support.' }
準備好不知所措。當我說下面的話時,我的學生總是生我的氣:
如果你咋看之下并不是什么都懂,那沒問題。只要使用一段時間,你就會發現為什么要那樣。
順便說一下,這可以稱為自上而下的學習,我非常喜歡。
路由處理程序就是這個樣子:
我希望你能讀下代碼中的注釋,我認為那比我在這里解釋更有意義。現在,讓我們看下實際的路由文件是什么樣子:
在這些例子中,我沒有做任何有實際要求的事情,我只是假設不同的錯誤場景。例如,GET /city 在第 3 行結束,POST /city 在第 8 號結束等等。這也適用于查詢參數,例如,GET /city?startsWith=R。本質上,你會有一個未處理的錯誤,前端會收到:
{ error: 'GENERIC', description: 'Something went wrong. Please try again or contact support.' }
或者你將手動拋出 CustomError,例如:
throw new CustomError('MY_CODE', 400, 'Error description')
上述代碼會轉換成:
{ error: 'MY_CODE', description: 'Error description' }
既然我們有了這個漂亮的后端設置,我們就不會再把錯誤日志泄漏到前端,而總是返回有用的信息,說明出現了什么問題。
確保你已經在 GitHub(https://github.com/gisderdube/graceful-error-handling) 上看過完整的庫。你可以把它用在任何項目中,并根據自己的需要來修改它!
下一個也是最后一個步驟是管理前端的錯誤。這里,你要使用第一部分描述的工具處理由前端邏輯產生的錯誤。不過,后端的錯誤也要顯示。首先,讓我們看看如何顯示錯誤。如前所述,我們將使用 React 進行演練。
把錯誤保存在 React 狀態中
和其他數據一樣,錯誤和錯誤消息會變化,因此,你想把它們放在組件狀態中。在默認情況下,你想要在加載時重置錯誤,以便用戶第一次看到頁面時,不會看到錯誤。
接下來我們必須澄清的是不同錯誤類型及與其匹配的可視化表示。就像在后端一樣,有 3 種類型:
2 和 3 非常類似,雖然源頭不一樣,但如果你愿意,就可以在同樣的 state 中處理。我們將從代碼中看下如何實現。
我將使用 React 的原生 state 實現,但是,你還可以使用類似 MobX 或 Redux 這樣的狀態管理系統。
全局錯誤
通常,我將把這些錯誤保存在最外層的有狀態組件中,并渲染一個靜態 UI 元素,這可能是屏幕頂部的一個紅色橫幅、模態或其他什么東西,設計實現由你決定。
讓我們看下代碼:
正如你看到的那樣,Application.js 中的狀態存在錯誤。我們也有方法可以重置并改變錯誤的值。我們把值和重置方法傳遞給 GlobalError 組件,在點擊'x'時,該組件會顯示錯誤并重置它。讓我們看看 GlobalError 組件:
你可以看到,在第 5 行,如果沒有錯誤,我們就不做任何渲染。這可以防止我們的頁面上出現一個空的紅框。當然,你可以改變這個組件的外觀和行為。例如,你可以將“x”替換為 Timeout,幾秒鐘后重置錯誤狀態。
現在,你已經準備好在任何地方使用全局錯誤狀態了,只是從 Application.js 把 _setError 向下傳遞,而且,你可以設置全局錯誤,例如,當一個請求從后端返回了字段 error: 'GENERIC'。例如:
如果你比較懶,到這里就可以結束了。即使你有具體的錯誤,你總是可以改變全局錯誤狀態,并把錯誤提示框顯示在頁面頂部。不過,我將向你展示如何處理和顯示具體的錯誤。為什么?首先,這是關于錯誤處理的權威指南,所以我不能停在這里。其次,如果你只是把所有的錯誤都作為全局錯誤來顯示,那么體驗人員會瘋掉。
處理具體的請求錯誤
和全局錯誤類似,我們也有位于其他組件內部的局部錯誤狀態,過程相同:
有件事要記住,清除錯誤通常有一個不同的觸發器。用' x '刪除錯誤是沒有意義的。關于這一點,在發出新請求時清除錯誤會更有意義。你還可以在用戶進行更改時清除錯誤,例如當修改輸入值時。
源于前端的錯誤
如前所述,這些錯誤可以使用與處理后端具體錯誤相同的方式(狀態)進行處理。這次,我們將使用一個有輸入字段的示例,只允許用戶在實際提供以下輸入時刪除一個城市:
使用錯誤代碼實現錯誤國際化
也許你一直想知道為什么我們有這些錯誤代碼,例如 GENERIC ,我們只是顯示從后端傳遞過來的錯誤描述。現在,隨著你的應用越來越大,你就會希望征服新的市場,并在某個時候面臨多種語言支持的問題。如果你到了這個時候,你就可以使用前面提到的錯誤代碼使用用戶的語言來顯示恰當的描述。
我希望你對如何處理錯誤有了一些了解。忘掉 console.error(err),它現在已經是過去時了。可以使用它進行調試,但它不應該出現在最終的產品構建中。為了防止這種情況,我建議你使用日志庫,我過去一直使用 loglevel,我對它非常滿意。
英文原文
https://levelup.gitconnected.com/the-definite-guide-to-handling-errors-gracefully-in-javascript-58424d9c60e6
者 | 魔笛
責編 | 郭芮
2015年3月,Facebook正式發布react-native,只支持iOS平臺;2015年9月,Facebook發布了React Native for Android,讓這一技術正式成為跨平臺開發框架。
我們團隊是在2016年中期開始接觸并使用react-native, 起初團隊有很多反對聲,其中:
當時選擇react-native的幾個重要因素:
移動框架學習套路
每次接觸一個新技術、新框架我們總是一頭霧水。其實是有套路的,有經驗的程序員會說這就是思想,下面主要從移動開發這幾個方面調研:
計算機語言工具
環境搭建
UI繪制
基本布局方式
基本tab + navigator的APP框架搭建
網絡請求(http, https, 上傳,下載等)
緩存, 本地存儲
圖片
平臺特性處理:例如推送,支付等等iOS,安卓不同的平臺代碼如何處理
調試工具:好的調試工具不但可以事半功倍,還可以給開發者愉悅的心情開發
靜態代碼檢查(這個對于解釋型的JS語言很重要)
Unit Test
CI集成方式
以上幾個方面都研究明白了,整個react-native生產鏈路就調研完成了。
技術棧
針對上面的過程總結一下技術棧。
需要的語言&框架
如何進行環境搭建
如何進行UI繪制與布局
React Native 提供豐富的基礎組件庫,使用Flexbox布局規則。采用jsx更直觀的表達用戶界面結構。
import React, {Component} from 'react' import {View, Text, Button, StyleSheet} from 'react-native' export class Home extends Component { state={ number: 0 } _increase() { const { number }=this.state this.setState({number: number + 1}) } _decrease() { const { number }=this.state this.setState({number: number - 1}) } render() { const { number }=this.state return ( <View style={styles.container}> <Text>Home</Text> <Button title="加" onPress={this._increase.bind(this)}/> <Button title="減" onPress={this._decrease.bind(this)}/> <Text>{number}</Text> </View> ) } } let styles=StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff' } })
上例繪制一個簡單的頁面, View是最基礎的UI組件,并且支持Flexbox布局,Text是用于顯示文本的組件,Button從命名上就可以明確是按鈕組件,StyleSheet 提供了一種類似 CSS 樣式表的抽象。
調試
調試是開發流程中最重要的事情,下面兩個工具給RN開發帶來了超爽的體驗
靜態代碼檢查
JavaScript是解釋型語言,編譯過程只有詞法分析和語法分析,并沒有詞法檢查,eslint對于JS的意義格外重要:
代碼質量的保證
CI 可以用以下工具
轉場
狀態管理
如何做網絡請求
fetch('https://mywebsite.com/endpoint/', { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ firstParam: 'yourValue', secondParam: 'yourOtherValue', }), });
如何本地存儲
import { AsyncStorage } from 'react-native' let kLoginInfo='@login:info' //存儲 AsyncStorage.setItem(kLoginInfo, JSON.stringify(loginInfo)) //刪除 AsyncStorage.removeItem(kLoginInfo) //加載 AsyncStorage.getItem(kLoginInfo)
熱更新
native組件開發
未來展望
論成熟度、穩定性,RN 比不上iOS和Android原生,也存在很多奇怪的BUG,習慣了OC、Java語言開發的對于JS缺少類型系統很失望,對于手動操作動畫也依然無解,長列表性能問題無解、手勢操作不夠靈活、iOS podfile維護也不給力........等等問題。
很多單位采用模塊化方案,讓App有RN的能力,讓業務交互簡單的模塊來用react-native開發。但是Facebook依然很努力地在改變,2018年對react-native有了一次大的重構,目的更輕量化并能更好地適應現有的原生應用,更符合JS的生態系統。
對于移動開發者而言,react-native只是開發箱中其中一種工具。豐富自己工具箱,才能有更寬的視野和更多的開發思路。
作者:魔笛,從事移動端開發7+年的經驗,目前在某互聯網金融公司作為iOS端leader工作。
聲明:本文為作者投稿,版權歸對方所有。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。