Alex

有的故事值得一直说下去.
Home » Latest Posts

Expo 提供的“后台”解决方案 Expo 提供了一些 API 来在 App 处于后台时执行有限的任务,而不是真正的“常驻进程”。

  1. 后台任务 - expo-task-manager + expo-background-fetch 这是 Expo 中最接近“后台进程”的官方解决方案。它允许你在后台定期执行一小段代码(例如每15分钟一次)。

典型用例:

定期获取最新数据(如天气、消息)

定期上报位置信息

处理待推送的通知

工作原理:

你定义一个“任务”(Task),即一个异步函数。

系统(iOS/Android)会在它认为合适的时间(考虑用户行为、电量等)唤醒你的 App 并执行这个任务。

任务执行时间非常有限(在 iOS 上通常只有 30 秒左右)。

简单示例:

javascript // 1. 导入 import * as BackgroundFetch from 'expo-background-fetch'; import * as TaskManager from 'expo-task-manager';

// 2. 定义任务 const BACKGROUND_FETCH_TASK = 'my-background-task';

TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => { const now = Date.now(); console.log(在后台运行!时间戳: ${now}); // 这里执行你的后台逻辑,例如调用 API、更新本地存储等 // 必须返回一个结果! return BackgroundFetch.BackgroundFetchResult.NewData; });

// 3. 注册任务 async function registerBackgroundFetchAsync() { return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, { minimumInterval: 15 * 60, // 15分钟(单位:秒)。实际间隔由系统决定。 stopOnTerminate: false, // 安卓专用:App 关闭后是否继续 startOnBoot: true, // 安卓专用:手机重启后是否启动 }); }

// 在您的 App 组件中调用 registerBackgroundFetchAsync() 2. 后台位置跟踪 - expo-location 如果你需要持续在后台获取用户位置(例如导航、健身应用),可以使用 expo-location。

工作原理:

它使用专门优化的后台位置更新 API。

即使 App 在后台,系统也会持续发送位置更新。

这非常耗电,需要向用户明确请求权限并说明用途。

简单示例:

javascript import * as Location from 'expo-location';

// 请求权限并开始后台定位 await Location.requestBackgroundPermissionsAsync(); await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, { accuracy: Location.Accuracy.Balanced, // 其他配置选项... }); 3. 处理接收到的通知 - expo-notifications 当 App 在后台时收到推送通知,你可以配置一个“任务”来处理它(即使 App 完全关闭,在 Android 上也可以)。

工作原理:

当通知到达且 App 不在前台时,你可以设置一个后台处理函数。

这个函数可以执行一些操作,例如更新本地数据库、静默同步数据等。

简单示例:

javascript import * as Notifications from 'expo-notifications';

// 设置后台通知处理函数 Notifications.setNotificationHandler({ handleNotification: async () => ({ shouldShowAlert: true, shouldPlaySound: false, shouldSetBadge: false, }), });

// 监听收到的通知(即使App在后台或关闭) Notifications.addNotificationReceivedListener(notification => { console.log('收到通知:', notification); // 可以在这里执行后台逻辑 }); 开发版(Development Build)和裸应用(Bare Workflow)的进阶选项 如果你使用 expo prebuild 生成了一个裸应用(Bare Workflow)或使用开发版(Development Build),你就可以脱离 Expo Go 的限制,使用社区原生模块来实现更强大的后台功能。

常用社区库:

react-native-background-actions: 可以在后台执行 JavaScript 代码(甚至在通知栏显示一个持续运行的通知来保活)。

react-native-background-timer: 在后台实现定时器功能。

Headless JS(仅Android): React Native 官方提供的在后台运行 JS 代码的方案。

重要警告:

在 iOS 上,滥用后台进程会导致你的 App 审核被拒。你必须遵循 Apple 的 Background Execution Guidelines。通常只允许音频播放、位置更新、VoIP、后台获取等特定场景。

过度活跃的后台进程会严重消耗用户电量,导致用户体验极差。

总结与建议 你的需求 推荐方案 定期同步少量数据(< 30秒) expo-task-manager + expo-background-fetch 持续获取地理位置 expo-location 的后台位置API 收到推送后执行简单操作 expo-notifications 的后台处理函数 需要长时间运行复杂逻辑(如音乐播放、录音) 使用 开发版(Development Build) 或 裸应用(Bare Workflow),并集成像 react-native-background-actions 这样的社区库。务必注意平台政策。 对于大多数 Expo 开发者来说,expo-background-fetch 是处理全局后台任务的首选和官方支持的方式。它平衡了功能与平台合规性。如果你的需求超出了它的能力范围,那么就需要考虑转向开发版或裸应用,并准备好应对更复杂的原生配置和平台审核挑战。

  1. 安装依赖 bash npx expo install expo-sqlite npm install drizzle-orm npm install -D @types/better-sqlite3 drizzle-kit
  2. 定义数据库模式 (schema.ts) typescript // database/schema.ts import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

// 定义一张表 export const todos = sqliteTable('todos', { id: integer('id').primaryKey({ autoIncrement: true }), text: text('text').notNull(), isCompleted: integer('is_completed', { mode: 'boolean' }).notNull().default(false), createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()), });

// 导出模式类型 export type InsertTodo = typeof todos.$inferInsert; export type SelectTodo = typeof todos.$inferSelect; 3. 创建数据库连接和客户端 typescript // database/index.ts import { openDatabaseSync } from 'expo-sqlite'; import { drizzle } from 'drizzle-orm/expo-sqlite';

// 打开 Expo 的 SQLite 数据库 const expoDb = openDatabaseSync('myApp.db'); // 创建 Drizzle 客户端 export const db = drizzle(expoDb); 4. 使用 Drizzle Kit 进行迁移和检查(可选但推荐) 在 package.json 中添加脚本:

json { "scripts": { "generate": "drizzle-kit generate:sqlite --schema=./database/schema.ts --out=./database/migrations", "studio": "drizzle-kit studio --schema=./database/schema.ts" } } npm run generate:根据你的 schema.ts 生成 SQL 迁移文件。

npm run studio:启动 Drizzle Studio,一个强大的 GUI 来查看和编辑你的数据库。

  1. 在应用中进行数据库操作 typescript import { db } from './database'; import { todos, type InsertTodo } from './database/schema';

// 查询 const allTodos = await db.select().from(todos).all();

// 插入 const newTodo: InsertTodo = { text: 'Learn Drizzle ORM' }; const insertedTodo = await db.insert(todos).values(newTodo).returning().get();

// 更新 await db.update(todos) .set({ isCompleted: true }) .where(eq(todos.id, 1)) .run();

// 使用复杂查询 import { desc, eq, and, or } from 'drizzle-orm'; const importantTodos = await db.select() .from(todos) .where( or( eq(todos.isCompleted, false), eq(todos.text, '%urgent%') ) ) .orderBy(desc(todos.createdAt)) .all(); 总结 追求最佳类型安全、性能和现代开发体验:毫不犹豫地选择 Drizzle ORM。

构建具有复杂离线同步功能的大型应用:深入评估 WatermelonDB。

避免使用 TypeORM:它在 React Native 环境中的体验不佳。

除非万不得已,不要直接写裸 SQL:这会让代码变得难以维护。

对于绝大多数 Expo 开发者来说,expo-sqlite + Drizzle ORM 是当前技术栈下的黄金标准,能为你提供高效、可靠且愉快的开发体验。

在 Expo 中实现不识别框外的条码,可以通过设置扫描区域来实现。以下是一种基于expo-camera库的常见实现方式:

首先,确保你已经安装了expo-camera库。如果没有安装,可以使用以下命令进行安装:

bash npm install expo-camera

bash yarn add expo-camera

然后,在你的 React Native 组件中,可以按照以下方式设置扫描区域:

jsx import React, { useState, useEffect } from'react'; import { View, StyleSheet } from'react-native'; import { CameraView, useCameraPermissions } from "expo-camera";

export default function ScanCode() { const [permission, requestPermission] = useState(null); const [cameraRef, setCameraRef] = useState(null); const [scanRegion, setScanRegion] = useState({ x: 0.2, y: 0.2, width: 0.6, height: 0.6 });

useEffect(() => { (async () => { const { status } = await requestPermission(); setPermission(status === 'granted'); })(); }, [requestPermission]);

if (permission === null) { return null; }

if (permission === 'denied') { return <View><Text>相机权限被拒绝</Text></View>; }

return ( <View style={styles.container}> <CameraView ref={setCameraRef} style={styles.camera} barcodeTypes={["qr", "code128"]} // 设置你要识别的条码类型 scanRegion={scanRegion} // 设置扫描区域 /> </View> ); }

const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, camera: { flex: 1, width: '100%', height: '100%' } });

在上述代码中,通过scanRegion属性来设置扫描区域。scanRegion是一个对象,其属性x、y、width、height分别表示扫描区域在相机画面中的水平位置、垂直位置、宽度和高度,取值范围均为 0 到 1。这样设置后,Expo 只会识别指定扫描区域内的条码,而不会识别框外的条码。

2025-07-30T02:29:31.png

2025-07-30T02:29:51.png

大模型爬虫—ScrapeGraphAI 一、介绍 ScrapeGraphAI是一个网络爬虫 Python 库,使用大型语言模型和直接图逻辑为网站和本地文档(XML,HTML,JSON 等)创建爬取管道。 只需告诉库您想提取哪些信息,它将为您完成!

scrapegraphai有三种主要的爬取管道可用于从网站(或本地文件)提取信息:

SmartScraperGraph: 单页爬虫,只需用户提示和输入源; SearchGraph: 多页爬虫,从搜索引擎的前 n 个搜索结果中提取信息; SpeechGraph: 单页爬虫,从网站提取信息并生成音频文件。 SmartScraperMultiGraph: 多页爬虫,给定一个提示 可以通过 API 使用不同的 LLM,如 OpenAI,Groq,Azure 和 Gemini,或者使用 Ollama 的本地模型。

二、准备工作 12.1 安装ollama 点击前往网站 ollama.com/ ,下载ollama软件,支持win、Mac、linux

2.2 下载LLM ollama软件目前支持多种大模型, 如阿里的(qwen、qwen2)、meta的(llama3),

以llama3为例,根据自己电脑显存性能, 选择适宜的版本。如果不知道选什么,那就试着安装,不合适不能用再删除即可。

打开电脑命令行cmd(mac是terminal), 网络是连网状态,执行模型下载(安装)命令 arduino 体验AI代码助手 代码解读复制代码ollama pull llama3 ollama pull qwen2 ollama pull nomic-embed-text

等待 llama3、 nomic-embed-text 下载完成。 2.3 安装python包 在python中调用ollama服务,需要ollama包。 打开电脑命令行cmd(mac是terminal), 网络是连网状态,执行安装命令 体验AI代码助手 代码解读复制代码pip3 install ollama

2.4 启动ollama服务 在Python中调用本地ollama服务,需要先启动本地ollama服务, 打开电脑命令行cmd(mac是terminal), 执行 arduino 体验AI代码助手 代码解读复制代码ollama run llama3

这样就启动了ollama 服务,并且使用的是llama3的模型 或者你可以直接使用 启动服务,但是不指定模型 vbscript 体验AI代码助手 代码解读复制代码 ollama server

ini 体验AI代码助手 代码解读复制代码2024/06/14 14:52:24 routes.go:1011: INFO server config env="map[OLLAMA_DEBUG:false OLLAMA_FLASH_ATTENTION:false OLLAMA_HOST:http://127.0.0.1:11434 OLLAMA_KEEP_ALIVE: OLLAMA_LLM_LIBRARY: OLLAMA_MAX_LOADED_MODELS:1 OLLAMA_MAX_QUEUE:512 OLLAMA_MAX_VRAM:0 OLLAMA_MODELS:/Users/deng/.ollama/models OLLAMA_NOHISTORY:false OLLAMA_NOPRUNE:false OLLAMA_NUM_PARALLEL:1 OLLAMA_ORIGINS:[http://localhost https://localhost http://localhost:* https://localhost:* http://127.0.0.1 https://127.0.0.1 http://127.0.0.1:* https://127.0.0.1:* http://0.0.0.0 https://0.0.0.0 http://0.0.0.0:* https://0.0.0.0:* app://* file://* tauri://*] OLLAMA_RUNNERS_DIR: OLLAMA_TMPDIR:]" time=2024-06-14T14:52:24.742+08:00 level=INFO source=images.go:725 msg="total blobs: 18" time=2024-06-14T14:52:24.742+08:00 level=INFO source=images.go:732 msg="total unused blobs removed: 0" time=2024-06-14T14:52:24.743+08:00 level=INFO source=routes.go:1057 msg="Listening on 127.0.0.1:11434 (version 0.1.44)" time=2024-06-14T14:52:24.744+08:00 level=INFO source=payload.go:30 msg="extracting embedded files" dir=/var/folders/y0/4gqxky0s2t94x1c1qhlwr6100000gn/T/ollama4239159529/runners time=2024-06-14T14:52:24.772+08:00 level=INFO source=payload.go:44 msg="Dynamic LLM libraries [metal]" time=2024-06-14T14:52:24.796+08:00 level=INFO source=types.go:71 msg="inference compute" id=0 library=metal compute="" driver=0.0 name="" total="72.0 GiB" available="72.0 GiB"

cmd(mac是terminal)看到如上的信息,说明本地ollama服务已开启。 2.5 安装scrapegraphai及playwright 电脑命令行cmd(mac是terminal), 网络是连网状态,执行安装命令 体验AI代码助手 代码解读复制代码pip install scrapegraphai

之后继续命令行cmd(mac是terminal)执行 体验AI代码助手 代码解读复制代码playwright install

等待安装完成后,进行实验 三、实验 3.1 案例1 以 https://textdata.cn/blog/ 博客为例,假设我想获取标题、日期、文章链接,

代码如下: python 体验AI代码助手 代码解读复制代码from scrapegraphai.graphs import SmartScraperGraph ​ ​ graph_config = {    "llm": {        "model": "ollama/llama3",        "temperature": 0,        "format": "json",  # Ollama 需要显式指定格式        "base_url": "http://localhost:11434",  # 设置 Ollama URL   },    "embeddings": {        "model": "ollama/nomic-embed-text",        "base_url": "http://localhost:11434",  # 设置 Ollama URL   },    "verbose": True, } ​ smart_scraper_graph = SmartScraperGraph(    prompt="返回该网站所有文章的标题、日期、文章链接",    # 也接受已下载的 HTML 代码的字符串    #source=requests.get("https://textdata.cn/blog/").text,    source="https://textdata.cn/blog/",    config=graph_config ) ​ result = smart_scraper_graph.run() print(result)

Run sql 体验AI代码助手 代码解读复制代码--- Executing Fetch Node --- --- Executing Parse Node --- --- Executing RAG Node --- --- (updated chunks metadata) --- --- (tokens compressed and vector stored) --- --- Executing GenerateAnswer Node --- Processing chunks: 100%|█████████████████████████| 1/1 [00:00<00:00, 825.81it/s] ​ {'articles': [{'title': 'LIST | 社科(经管)数据挖掘文献资料汇总', 'date': '2024-04-15', 'link': 'https://textdata.cn/blog/management_python_course/'},

  {'title': 'LIST| 文本分析代码资料汇总', 
  'date': '2024-04-15',
  'link':'https://textdata.cn/blog/text_analysis_code_list_about_ms/'}, 
  
  {'title': '实验 | 使用本地大模型从文本中提取结构化信息', 
  'date': '2024-06-14', 
  'link': 'https://textdata.cn/blog/2024-06-14-using-large-language-model-to-extract-structure-data-from-raw-text/'}, 
  
  {'title': '2023 | 文本分析在经管研究中的应用', 
  'date': '2023-11-05', 
  'link': 'https://textdata.cn/blog/2023-11-05-xjtu-text-mining-in-ms/'}, 
  
  {'title': '经管类 | 含 经济日报/经济观察报/中国工业报/中国贸易报/中国消费者报 等 10+ 家媒体(2024.05)', 
  'date': '2024-06-12', 
  'link': 'https://textdata.cn/blog/2024-06-12-national-level-economic-daily-news-dataset/'}]}

如果运行过程中报下面这个错误,那是因为下载所需依赖失败了,你需要打开vpn less 体验AI代码助手 代码解读复制代码   raise EnvironmentError( OSError: Can't load tokenizer for 'gpt2'. If you were trying to load it from 'https://huggingface.co/models', make sure you don't have a local directory with the same name. Otherwise, make sure 'gpt2' is the correct path to a directory containing all relevant files for a GPT2TokenizerFast tokenizer.

3.2 案例2 采集豆瓣读书 https://book.douban.com/top250 中的 名字、作者名、评分、书籍链接 等信息。

python 体验AI代码助手 代码解读复制代码from scrapegraphai.graphs import SmartScraperGraph ​ ​ graph_config = {    "llm": {        "model": "ollama/llama3",        "temperature": 0,        "format": "json",  # Ollama 需要显式指定格式        "base_url": "http://localhost:11434",  # 设置 Ollama URL   },    "embeddings": {        "model": "ollama/nomic-embed-text",        "base_url": "http://localhost:11434",  # 设置 Ollama URL   },    "verbose": True, } ​ ​ smart_scraper_graph2 = SmartScraperGraph(    prompt="返回该页面所有书的名字、作者名、评分、书籍链接",    source="https://book.douban.com/top250",    config=graph_config ) ​ result2 = smart_scraper_graph2.run() print(result2)

Run sql 体验AI代码助手 代码解读复制代码--- Executing Fetch Node --- --- Executing Parse Node --- --- Executing RAG Node --- --- (updated chunks metadata) --- --- (tokens compressed and vector stored) --- --- Executing GenerateAnswer Node --- Processing chunks: 100%|████████████████████████| 1/1 [00:00<00:00, 1474.79it/s] {}

采集失败,返回空。 将大模型llama3改为qwen2 python 体验AI代码助手 代码解读复制代码from scrapegraphai.graphs import SmartScraperGraph ​ ​ graph_config2 = {   "llm": {       "model": "ollama/qwen2",       "temperature": 0,       "format": "json", # Ollama 需要显式指定格式       "base_url": "http://localhost:11434", # 设置 Ollama URL   },   "embeddings": {       "model": "ollama/nomic-embed-text",       "base_url": "http://localhost:11434", # 设置 Ollama URL   },   "verbose": True, } ​ ​ smart_scraper_graph3 = SmartScraperGraph(   prompt="返回该页面所有书的名字、作者名、评分、书籍链接",   source="https://book.douban.com/top250",   config=graph_config2 ) ​ result3 = smart_scraper_graph3.run() print(result3)

Run sql 体验AI代码助手 代码解读复制代码--- Executing Fetch Node --- --- Executing Parse Node --- --- Executing RAG Node --- --- (updated chunks metadata) --- --- (tokens compressed and vector stored) --- --- Executing GenerateAnswer Node --- Processing chunks: 100%|████████████████████████| 1/1 [00:00<00:00, 1102.60it/s] {'urls': ['https://book.douban.com/subject/10554308/', 'https://book.douban.com/subject/1084336/', 'https://book.douban.com/subject/1084336/', 'https://book.douban.com/subject/1046209/', 'https://book.douban.com/subject/1046209/', 'https://book.douban.com/subject/1255625/', 'https://book.douban.com/subject/1255625/', 'https://book.douban.com/subject/1060068/', 'https://book.douban.com/subject/1060068/', 'https://book.douban.com/subject/1449351/', 'https://book.douban.com/subject/1449351/', 'https://book.douban.com/subject/20424526/', 'https://book.douban.com/subject/20424526/', 'https://book.douban.com/subject/29799269/', 'https://book.douban.com/subject/1034062/', 'https://book.douban.com/subject/1229240/', 'https://book.douban.com/subject/1237549/', 'https://book.douban.com/subject/1078958/', 'https://book.douban.com/subject/1076932/', 'https://book.douban.com/subject/1075440/', 'https://book.douban.com/subject/1076932/', 'https://book.douban.com/subject/1078958/', 'https://book.douban.com/subject/1076932/', 'https://book.douban.com/subject/1078958/', 'https://book.douban.com/subject/1076932/', 'https://book.douban.com/subject/1078958/', 'https://book.douban.com/subject/1076932/'], 'images': ['https://img1.doubanio.com/view/subject/s/public/s1078958.jpg', 'https://img1.doubanio.com/view/subject/s/public/s1076932.jpg', 'https://img1.doubanio.com/view/subject/s/public/s1447349.jpg']}

采集到一些信息,但没有书名、作者等信息。

作者:刘不二 链接:https://juejin.cn/post/7453300179253837874 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Expo 应用内更新实现指南 Expo 提供了两种主要的应用内更新方案,适用于不同的使用场景:

  1. Expo 托管更新 (OTA Updates) 适用于使用 Expo 托管服务的项目:

javascript import * as Updates from 'expo-updates';

async function checkForUpdate() { try { const update = await Updates.checkForUpdateAsync(); if (update.isAvailable) { await Updates.fetchUpdateAsync(); // 可以立即重载应用或通知用户稍后重载 Updates.reloadAsync(); } } catch (e) { // 处理错误 console.error('更新检查失败:', e); } } 2025-07-10T01:07:06.png 2. 使用 EAS 更新 (推荐) 对于使用 EAS Build 的项目,Expo 提供了更强大的更新系统:

javascript import { Updates } from 'expo';

async function checkEASUpdate() { try { const { isAvailable, manifest } = await Updates.checkForUpdateAsync();

if (isAvailable) {
  // 显示更新提示
  Alert.alert(
    '更新可用',
    '发现新版本,是否立即更新?',
    [
      { text: '稍后', style: 'cancel' },
      { text: '更新', onPress: async () => {
        await Updates.fetchUpdateAsync();
        Updates.reloadAsync();
      }}
    ]
  );
}

} catch (error) { console.error('更新检查错误:', error); } } 2025-07-10T01:05:53.png 配置要点 app.json/app.config.js 中配置更新策略:

json { "expo": { "updates": { "enabled": true, "checkAutomatically": "ON_LOAD", // 或 "ON_ERROR_RECOVERY", "WIFI_ONLY" "fallbackToCacheTimeout": 0 } } } 发布更新:

bash

发布到默认频道

expo publish

或使用 EAS 更新

eas update --branch production --message "修复了一些bug" 2025-07-10T01:06:35.png 最佳实践 在应用启动时检查更新 (App.js 或根组件中)

考虑用户的网络环境,大更新可提示用户连接WiFi

提供跳过更新的选项(对于非关键更新)

记录更新失败情况以便排查问题

注意事项 原生代码变更仍需通过应用商店更新

测试时可以使用 expo publish --release-channel test 发布到测试频道

更新大小限制通常为50MB(取决于Expo服务配置)

如需更复杂的更新策略,可以考虑实现自定义更新UI和逻辑。

Life is fantastic
🥕 More