Android開發平臺
谷歌在2007年發佈Android 是一個開源的基於 Linux 的移動設備操作系統
支持的設備: phone…
語言: 開發語言是java , 後來因爲甲骨文準備對android java收費, 又開始向Kotlin轉移
IDE: 開發工具以前是eclipse+sdk, 後來谷歌退出了獨立的IDE, Android Studio
打包: 使用簽名打包生成.apk文件, 可作爲app, 安裝在android系統.
可用的平臺框架
現在開發app主要有四種可選方案
首先是原生, 性能最好, 可是針對不同平臺業務代碼需要重複開發,
並且每次發版都需要經過應用市場審覈
所以爲了解決這個問題, 衍生出Cordove+ionic+web的方案,
cordova 是Apache的開源框架, 由PhoneGap演化而來,
提供的是跨平臺運行在各種設備的app殼
Ionic 爲Web頁面提供UI界面和交互.
然而web版本性能方面有些欠缺.
所以Facebook推出ReactNative採用virtual DOM與native UI控件進行綁定,
因此很好待解決了性能問題.
Flutter使用dart 替換掉JS和V8, 性能據說有有更好的提升.
Android 架構
> Linux內核層
主要包含硬件相關的驅動, 電源管理, 以及內核本身的文件管理, 線程調度, 權限管理, 內存管理等.
> 硬件抽象層
通過抽象封裝硬件“驅動”的接口來進一步降低Android系統與硬件的耦合度;
> 運行庫:
運行時: 核心庫, Dalvik 虛擬機(使各java應用擁有獨立的進程)
C++庫: Sqlite, webkit, media, audio, ssl, openGL, freetype
> JAVA的應用框架:
Activity, Windows, View, notification, package, Telephony, location, resource.
> APP應用
短信, 瀏覽器, 撥號, 計算器, ….
從Js 層到Java層
前面說明了android系統從底層到app的整體架構,
接下來說明一下react-native 實現的android app是怎麼交互的.
畫這個圖的人把上下顛倒了一下.
> Java層:
該層主要提供了Android的UI渲染器UIManager(將JavaScript映射成Android Widget)
> Android Widget 控件
Fresco圖片加載, okhttp 提供http請求,
對於ios, 或者windows, 這一層都是native層的類似模塊.
> JSEngine:
提供js運行環境
> JSBridage:
負責上下層的通信.
> JavaScript層:
運行的是react native 的js代碼, 包含時間分發, js組件 等
Android Studio開發工具
谷歌提供了統一的誇平臺的IDE Android Studio來開發安卓應用, 包含
React Native開發過程中, 這個IDE使用主要是跟模擬器相關的,
Environment環境配置
環境上, 需要安裝react native命令行工具, python2, JDK和android studio.
添加環境變量, 給android studio安裝插件和包.
其中包含sdk, 平臺工具, intel硬件加速管理, 模擬器,
27版的api, 從右圖可以看到27對應的是android8.1, 之所以不使用最新的28, 是因爲目前還不夠穩定.
x86的系統, 用於模擬器啓動android.
安裝好react-native命令行工具,
就可以創建出工程了.
工程裏包含android的工程, ios的工程,
依賴的模塊, 和app.js, index.js.
進入工程目錄, 就可以跑起來打包安裝了.
Emulator模擬器
安裝或調試之前需要先配一個模擬器
或者將一個android設備配置mtp權限通過usb鏈接電腦.
ADB(Android Debug Bridge)調試器
path添加: AppData\Local\Android\Sdk\platform-tools
查看設備: adb devices
遠程: adb shell
安裝: adb install xxx.apk
卸載: adb uninstall xxx.apk
下載: adb pull
上傳: adb push
日誌: adb logcat
Android 的一個通用命令行工具,
幫助PC與模擬器實例或連接的 Android 設備進行通信
首先添加環境變量path添加androidsdk的工具路徑
然後就可以通過命令行控制了
Adb會在5037端口運行起來一個負責電腦與設備通信的服務.
AndroidManifest.xml 應用初始化清單
在 Android 系統啓動應用組件之前,
系統通過讀取應用的清單, 得知以下信息:
1. 報名, 版本名, 版本號, 默認安裝位置
2. 硬件功能: 如相機, 藍牙, GPS等.
3. 用戶權限: 流量, 聯繫人, 相冊, 讀寫存儲等.
4. 需要鏈接的API庫.
5. 啓動狀態, 比如橫屏, 豎屏, 感應, 鍵盤等.
這裏面還有很多節點和屬性, 這裏就不一一例舉了.
link鏈接
鏈接的意思是在android 和 ios 工程中引入對某個native庫的導入和依賴.
如果手動修改需要找到好幾個文件一一添加.
使用命令則可以自動完成這個步驟.
這樣做的目的是, 減少不必要庫的加載, 從而縮小打包大小.
react-native link xxx
android/app/build.gradle
android/app/src/main/java/com/AppName/MainApplication.java
android/settings.gradle
ios/AppName.xcodeproj/project.pbxproj
Touchable可觸摸組件
TouchableHighlight
TouchableNativeFeedback
TouchableOpacity
TouchableWithoutFeedback
onLayout?: (event: LayoutChangeEvent) => void;onLongPress?: (event: GestureResponderEvent) => void;
onPress?: (event: GestureResponderEvent) => void;onPressIn?: (event: GestureResponderEvent) => void;onPressOut?: (event: GestureResponderEvent) => void;
pressRetentionOffset?: Insets;
delayLongPress?: number;
delayPressIn?: number;
delayPressOut?: number;
除了button控件, 大部分控件無法響應觸摸行爲.
所以需要依賴Touchable系列控件的包裹.
Scroll滾動
ScrollView
FlatList
SectionList
onContentSizeChange?: (w: number, h: number) => void;
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
onScrollBeginDrag?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
onScrollEndDrag?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
onMomentumScrollEnd?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
onMomentumScrollBegin?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
滑動操作主要是針對可滾動的控件
ScrollView是基本的
FlatList針對長列表做了內存優化, 僅針對可現實範圍內的部分加載到內存
SectionList也有同樣的優化, 不過SectionList有分組的功能, 每個組都有獨立的header和多行內容.
http 請求
http 請求, 可以使用fetch接口, 或者XMLHttpRequest
左邊是fetch, 右邊是XMLHttpRequest,
還可以使用第三方庫.
fetch("https://mywebsite.com/endpoint/", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
firstParam: "yourValue",
secondParam: "yourOtherValue"
})
}) .then(response => response.json())
.then(responseJson => {
return responseJson.movies;
})
.catch(error => {
console.error(error);
});
var request = new XMLHttpRequest();
request.onreadystatechange = e => {
if (request.readyState !== 4) {
return;
}
if (request.status === 200) {
console.log("success", request.responseText);
} else {
console.warn("error");
}
};
request.open("GET", "https://mywebsite.com/endpoint/");
request.send();
File文件訪問
\android\app\src\main\AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
import * as RNFS from "react-native-fs";
export let FileOpt = {
writeDataStr: (dataStr: string | void, pathName: string) => {
if (dataStr) {
RNFS.writeFile(
`${RNFS.DocumentDirectoryPath}/${pathName}`,
dataStr, 'utf8'
).catch((err)=>{…});
}
},
readDataStr: (pathName: string): Promise<string | void> => {
return RNFS.readFile(
`${RNFS.DocumentDirectoryPath}/${pathName}`, 'utf8'
);
}
}
非常奇怪的是RN官方未提供對文件讀寫的接口.
所以不得不使用第三方提供的庫.
這是我封裝的文件讀寫操作接口.
這裏需要說明的是, 寫單例不能使用class, 因爲會導致打包的apk閃退.
所以這裏使用的是普通object的寫法.
另外, android會限制app的權限, 所以需要在前面提到的AndroidManifest.xml中申明讀寫權限.
Database數據庫
但是關係型數據庫, 都是第三方提供的.
WatermelonDB
react-native-sqlite-storage
官方提供的數據庫比較簡陋, 只有key-value
watermelonDB
react-native-sqlite-storage
import { AsyncStorage } from "react-native“;
setCacheStr: async (keyName: string, valueStr: string | void | null) => {
if (keyName && valueStr) {
AsyncStorage.setItem(keyName, valueStr)
.catch((err)=>{
NativeSdk.log("[StorageOpt: setCacheStr] failed:", err);
});
}
},
getCacheStr: (keyName: string): Promise<string | null> => {
return AsyncStorage.getItem(keyName);
},
Navigation導航
官方提供的navigator
包含tab頁導航, 以及跨頁面的跳轉.
首先是, 定義名字和導航頁面的映射.
然後在根頁面層的props中可以獲取到navigator對象,
使用navigator對象就可以做跳轉操作了.
去到一個頁面, 然後返回到前一個頁面.
也經過多次跳轉, 之後回到最初的頁面.
const TabNavigator = createBottomTabNavigator({
Collection: {
screen: PhotosPage,
path: '/TabsCollection',
swipeEnabled: true,
navigationOptions: {
tabBarLabel: '照片',
tabBarIcon: ({ tintColor, focused }:any) => ( <Image …/>),
tabBarOptions: tabBarOptions
}
}
})
const tabPages = createAppContainer(TabNavigator);
const AppNavigator = createStackNavigator({
Home: tabPages,
Photo: BigPhotoPage,
Setting: SettingsPage,
About: AboutPage
}, {
initialRouteName: 'Home',
headerMode: 'none',
});
let PageRouterMain = createAppContainer(AppNavigator);
navigation.push("Photo", {
name: 'BigPhotoPage',
photoParams: photoParams,
photoList: photoList
})
navigation.goBack()
Photos手機照片
獲取相片就需要區分平臺了,
這裏獲取相冊訪問權限, 需要調用PermissionAndroid
獲取相機訪問權限, 也是類似.
獲取到權限之後, 調用CameraRoll.getPhotos纔不會發生異常.
在主線程外解碼圖片
圖片解碼有可能會需要超過一幀的時間。在 web 上這是頁面掉幀的一大因素,
因爲解碼是在主線程中完成的。然而在 React Native 中,
圖片解碼則是在另一線程中完成的。不需要改動代碼去額外處理。
if (this.getPlatform() === "android") {
let granted = null;
if (access === "read-storage") {
granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
{'title': '手機存儲', 'message': '訪問您的手機存儲'}
);
} else if (access === "camera") {
granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.CAMERA,
{'title': '手機攝像頭', 'message': '訪問您的手機攝像頭'}
);
}
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
NativeSdk.log(`requestPermision ${access} granted`); } else {
NativeSdk.log(`requestPermision ${access} timeout or denied`);
}
}
}
CameraRoll.getPhotos({
first: getNum,
assetType: 'Photos',
after: cursor
}).then((Photos)=>{
resolve(Photos);
}).catch(error => {
reject(error);
});
需要小心哪些坑?
- Promise代碼調試時, 大半代碼, 無法下斷點.
- 安裝版console.log無輸出, 需要額外寫文件.
- 啓動失敗, 也許只是需要刪除所有打包緩存文件(每個link過的), 重新運行.
- 幾乎一半的非官方庫沒有同時兼容ios, android.
- CameraRoll獲取的照片地址不是實際地址, 沒有後綴, 名字還經常會變.
- 設備的USB經常需要重複多連幾次.
- 包含某些庫第三方庫之後可能導致打包apk失敗.
- 日誌分散在好幾個窗口, 發生錯誤時不得不一個個檢查.