Skip to content

Commit

Permalink
feat: support native-request (#21)
Browse files Browse the repository at this point in the history
* feat: support native-request

* feat: initialize native request support on android

* feat: more graceful response design

* feat: support dynamic data response

* fix: sync request and response data structure on android

* fix: use okhttp to resolve slowliness issue

---------

Co-authored-by: klxiaoniu <[email protected]>
  • Loading branch information
ozline and klxiaoniu authored Dec 29, 2024
1 parent 9c0bad2 commit c8bd809
Show file tree
Hide file tree
Showing 14 changed files with 572 additions and 1 deletion.
99 changes: 98 additions & 1 deletion app/(tabs)/user.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,109 @@
import { Text } from 'react-native';
import { Buffer } from 'buffer';
import React, { useState } from 'react';
import { Alert, Button, Image, Text, View } from 'react-native';

import { ThemedView } from '@/components/ThemedView';
import { get, post } from '@/modules/native-request';

export default function HomePage() {
const [dictionary, setDictionary] = useState<{ [key: string]: string }>({});
const [imageUrl, setImageUrl] = useState<string | null>(null); // 用于显示验证码图片

const urlLoginCheck = 'https://jwcjwxt1.fzu.edu.cn/logincheck.asp';
const urlVerifyCode = 'https://jwcjwxt1.fzu.edu.cn/plus/verifycode.asp';
const urlGet = 'https://www.baidu.com';
const headers = {
Referer: 'https://jwch.fzu.edu.cn',
Origin: 'https://jwch.fzu.edu.cn',
Cookie: 'ASPSESSIONIDAGRSTDCC=JADPAJABIJOFHMALKENMNHCP',
};
const formData = {
Verifycode: '111',
muser: 'student-id',
passwd: 'student-password',
};

const handlePress = async (
url: string,
headers: Record<string, string>,
formData: Record<string, string>,
) => {
try {
const {
status: respStatus,
data: respData,
headers: respHeaders,
} = await post(url, headers, formData);
setDictionary(respHeaders);
Alert.alert(
'结果',
respStatus +
'\n' +
JSON.stringify(Buffer.from(respData).toString('utf-8')), // 这里默认了 PSOT 返回的是 JSON 数据
);
} catch (error) {
Alert.alert('错误', String(error));
}
};

const handlePressGet = async (
url: string,
headers: Record<string, string>,
isBinary = false, // 是否为二进制数据,如果是的话转为 base64输出(只是测试,我们认为二进制数据就是图片)
) => {
try {
const {
status: respStatus,
data: respData,
headers: respHeaders,
} = await get(url, headers);
setDictionary(respHeaders);
// 根据 Content-Type 处理响应数据(可能需要内置一个映射表?)
if (isBinary) {
// 图片
const base64Data = btoa(String.fromCharCode(...respData));
const imageUrl = `data:image/png;base64,${base64Data}`;
setImageUrl(imageUrl); // 保存图片 URL 到状态
} else {
// 其他(默认为文本)
const responseData = Buffer.from(respData).toString('utf-8'); // 使用 Buffer 解码
Alert.alert('结果', respStatus + '\n' + responseData);
}
} catch (error) {
Alert.alert('错误', String(error));
}
};

return (
<>
<ThemedView>
<Text>User</Text>
<Button
title="获取数据POST"
onPress={() => handlePress(urlLoginCheck, headers, formData)}
/>
<Button
title="获取数据GET"
onPress={() => handlePressGet(urlGet, headers)}
/>
<Button
title="获取验证码图片"
onPress={() => handlePressGet(urlVerifyCode, headers, true)}
/>
{Object.entries(dictionary).map(([key, value]) => (
<Text key={key}>
{key}: {value}
</Text>
))}
{imageUrl && ( // 如果有图片 URL
<View style={{ marginTop: 20, marginLeft: 20 }}>
<Image
source={{ uri: imageUrl }}
style={{ width: 120, height: 35 }}
resizeMode="stretch" // 使用 stretch 来确保图片填满指定的宽高
/>
</View>
)}
</ThemedView>
</>
);
Expand Down
22 changes: 22 additions & 0 deletions docs/most-used-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 常见指令

## 预构建

```bash
yarn prebuild:{target}

# yarn prebuild:ios or yarn:prebuild:android
```

## 安装依赖

```bash
yarn install # 安装本项目的依赖
npx pod-install # 安装iOS侧依赖,使用 CocoaPods
```

## 运行

```bash
yarn {target} # 和 npx expo run:{target} 相同,详情见 package.json
```
24 changes: 24 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 常见问题排查

## iOS

### 无法打开 iOS Simulator

关键词: xcrun simctl boot {id} exited with non-zero code:2
关键词: No such file or directory

> Opening on iOS...
> Error: xcrun simctl boot 9CEBE3B2-E677-4C8D-91C1-7463A13C83A1 exited with non-zero code: 2
> An error was encountered processing the command (domain=NSPOSIXErrorDomain, code=2):
> Unable to boot device because we cannot determine the runtime bundle.
> No such file or directory
打开一次`Simulator`这个程序,它会做一些初始化工作,打开后再运行就发现正常了

出现这个问题一般是版本更新导致的,而且看起来是 Apple 工程师的问题

### 如果你认为你的代码没有问题,但持续编译报错

删除本地的构建文件夹(如`ios``android`),重新执行预构建-安装依赖-运行

这个问题通常不会发生很多次,通常是和底层(指的是原生系统)做交互开发时会涉及
87 changes: 87 additions & 0 deletions modules/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Modules

这个目录是与原生应用做衔接而生,使用了 Expo Modules API

## 边写衔接需要注意的事项

最好先跟着官方的 Tutorial 提供的快速入门做一遍,然后看这个注意事项,再看下面的快速上手

1. 新增一个东西,需要同时提供 Android 和 iOS 的实现,然后在 src 文件夹中再做引用
2. **如果你不需要原生视图渲染,请删除所有和 View 有关的文件/代码,可以直接搜索然后删掉**
3. 本质上是提供了一个接口衔接,可以通过 Modules 调用原生的存储,而不是用浏览器的 local-storage
4. 除了存储外,也可以通过这个接口实现原生特性

## 快速上手

除了官方提供的 [Tutorial](https://docs.expo.dev/modules/get-started/#adding-a-new-module-to-an-existing-application),这里提供更加简洁的使用方式

### 创建新的模块

```bash
❯ npx create-expo-module@latest --local
Need to install the following packages:
[email protected]
Ok to proceed? (y) y

npm warn deprecated @xmldom/[email protected]: this version is no longer supported, please update to at least 0.8.*

The local module will be created in the modules directory in the root of your project. Learn more: https://expo.fyi/expo-module-local-autolinking.md

✔ What is the name of the local module? … native-request
✔ What is the native module name? … NativeRequest
✔ What is the Android package name? … com.west2online.nativerequest

✔ Downloaded module template from npm
✔ Created the module from template files

✅ Successfully created Expo module in modules/native-request

You can now import this module inside your application.
For example, you can add this line to your App.js or App.tsx file:
import NativeRequestModule './modules/native-request';

Learn more on Expo Modules APIs: https://docs.expo.dev/modules
Remember to re-build your native app (for example, with npx expo run) when you make changes to the module. Native code changes are not reloaded with Fast Refresh.
```

提炼几个关键的地方:`native module name``Android package name`,这个需要特别注意

这个提示后面还提供了使用方法,直接 import 这个 module 就可以了,后续实现都在这个模块里完成

### 常见结构

下面这个结构是已经删除了 View 后的结果,默认生成的会更多一些

```text
❯ tree native-request
native-request
├── android
│ ├── build.gradle
│ └── src
│ └── main
│ ├── AndroidManifest.xml // Android 核心配置文件
│ └── java
│ └── com
│ └── west2online
│ └── nativerequest
│ └── NativeRequestModule.kt // Android 模块代码
├── expo-module.config.json // Expo 的模块配置,通常不需要修改
├── index.ts // 此处进行统一声明暴露
├── ios
│ ├── NativeRequest.podspec // iOS 核心配置文件
│ └── NativeRequestModule.swift // iOS 模块代码
└── src
├── NativeRequest.types.ts
├── NativeRequestModule.ts
└── NativeRequestModule.web.ts
10 directories, 10 files
```

从常见结构也可以看出来,这个 module 实际上提供了 web、Android 和 iOS(含 tvOS)三端的实现,并汇总到`index.ts`文件中

### 编写模块设置

可以通过编辑`ios/{ModuleName}Module.swift``android/{Namespace}/{ModuleName}/{ModuleName}Module.kt`来实现

这里可以具体参考 Expo 提供的 [API Reference](https://docs.expo.dev/modules/module-api),就不多写了
48 changes: 48 additions & 0 deletions modules/native-request/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
apply plugin: 'com.android.library'

group = 'com.west2online.nativerequest'
version = '0.6.0'

def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
useCoreDependencies()
useExpoPublishing()

// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
// Most of the time, you may like to manage the Android SDK versions yourself.
def useManagedAndroidSdkVersions = false
if (useManagedAndroidSdkVersions) {
useDefaultAndroidSdkVersions()
} else {
buildscript {
// Simple helper that allows the root project to override versions declared by this library.
ext.safeExtGet = { prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
}
project.android {
compileSdkVersion safeExtGet("compileSdkVersion", 34)
defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 21)
targetSdkVersion safeExtGet("targetSdkVersion", 34)
}
}
}

android {
namespace "com.west2online.nativerequest"
defaultConfig {
versionCode 1
versionName "0.6.0"
}
lintOptions {
abortOnError false
}
}

dependencies {
// 解决请求很慢的问题(fastFallback)
implementation('com.squareup.okhttp3:okhttp:5.0.0-alpha.14')
}
2 changes: 2 additions & 0 deletions modules/native-request/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<manifest>
</manifest>
Loading

0 comments on commit c8bd809

Please sign in to comment.