ReactNative 開發Android App

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 DOMnative UI控件進行綁定,

因此很好待解決了性能問題.

Flutter使用dart 替換掉JSV8, 性能據說有有更好的提升.

 

Android 架構

 > Linux內核層

    主要包含硬件相關的驅動, 電源管理, 以及內核本身的文件管理, 線程調度, 權限管理, 內存管理等.

> 硬件抽象層

    通過抽象封裝硬件“驅動”的接口來進一步降低Android系統與硬件的耦合度;

> 運行庫:

    運行時核心庫, Dalvik 虛擬機(使各java應用擁有獨立的進程)

    C++Sqlite, webkit, media, audio, sslopenGL, freetype

> JAVA的應用框架: 

    Activity, Windows, View,  notification, package, Telephony, location, resource.

> APP應用

    短信, 瀏覽器, 撥號, 計算器, …. 

 

從Js 層到Java層

前面說明了android系統從底層到app的整體架構,

接下來說明一下react-native 實現的android app是怎麼交互的.

畫這個圖的人把上下顛倒了一下.

> Java層:

  該層主要提供了AndroidUI渲染器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來開發安卓應用, 包含

1.代碼編輯.
2.佈局預覽和編輯
3.性能分析
4.模擬器
5.編譯/調試
6.查看和操作android設備上的文件
 

React Native開發過程中, 這個IDE使用主要是跟模擬器相關的,

 

Environment環境配置

環境上, 需要安裝react native命令行工具, python2, JDKandroid 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);
        });

需要小心哪些坑?

  1. Promise代碼調試時, 大半代碼, 無法下斷點.
  2. 安裝版console.log無輸出, 需要額外寫文件.
  3. 啓動失敗, 也許只是需要刪除所有打包緩存文件(每個link過的), 重新運行.
  4. 幾乎一半的非官方庫沒有同時兼容ios, android.
  5. CameraRoll獲取的照片地址不是實際地址, 沒有後綴, 名字還經常會變.
  6. 設備的USB經常需要重複多連幾次.
  7. 包含某些庫第三方庫之後可能導致打包apk失敗.
  8. 日誌分散在好幾個窗口, 發生錯誤時不得不一個個檢查.

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章