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 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失败.
- 日志分散在好几个窗口, 发生错误时不得不一个个检查.
更多推荐
所有评论(0)