Skip to content

Commit 2faba85

Browse files
committed
添加 SelfAgent Java 原生版本
1 parent 83203d8 commit 2faba85

45 files changed

Lines changed: 1646 additions & 9 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build_on_colab.ipynb

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
{
2+
"nbformat": 4,
3+
"nbformat_minor": 0,
4+
"metadata": {
5+
"colab": {
6+
"name": "Build Android APK",
7+
"provenance": []
8+
},
9+
"kernelspec": {
10+
"name": "python3",
11+
"display_name": "Python 3"
12+
}
13+
},
14+
"cells": [
15+
{
16+
"cell_type": "markdown",
17+
"metadata": {},
18+
"source": [
19+
"# 在 Google Colab 上构建 Android APK\n",
20+
"\n",
21+
"## 使用步骤:\n",
22+
"1. 点击菜单 Runtime -> Run all\n",
23+
"2. 等待 15-20 分钟\n",
24+
"3. 下载生成的 APK 文件"
25+
]
26+
},
27+
{
28+
"cell_type": "code",
29+
"metadata": {},
30+
"source": [
31+
"# 1. 克隆你的 GitHub 仓库\n",
32+
"!git clone https://github.com/Michael-YuQ/python-calculator-android.git\n",
33+
"%cd python-calculator-android"
34+
],
35+
"execution_count": null,
36+
"outputs": []
37+
},
38+
{
39+
"cell_type": "code",
40+
"metadata": {},
41+
"source": [
42+
"# 2. 安装 Buildozer\n",
43+
"!pip install buildozer cython==0.29.33"
44+
],
45+
"execution_count": null,
46+
"outputs": []
47+
},
48+
{
49+
"cell_type": "code",
50+
"metadata": {},
51+
"source": [
52+
"# 3. 安装系统依赖\n",
53+
"!sudo apt-get update\n",
54+
"!sudo apt-get install -y git zip unzip openjdk-17-jdk wget autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev"
55+
],
56+
"execution_count": null,
57+
"outputs": []
58+
},
59+
{
60+
"cell_type": "code",
61+
"metadata": {},
62+
"source": [
63+
"# 4. 构建 APK(这一步需要 15-20 分钟)\n",
64+
"!buildozer android debug"
65+
],
66+
"execution_count": null,
67+
"outputs": []
68+
},
69+
{
70+
"cell_type": "code",
71+
"metadata": {},
72+
"source": [
73+
"# 5. 下载 APK\n",
74+
"from google.colab import files\n",
75+
"import os\n",
76+
"\n",
77+
"apk_files = [f for f in os.listdir('bin') if f.endswith('.apk')]\n",
78+
"if apk_files:\n",
79+
" files.download(f'bin/{apk_files[0]}')\n",
80+
" print(f'APK 已准备好下载: {apk_files[0]}')\n",
81+
"else:\n",
82+
" print('未找到 APK 文件')"
83+
],
84+
"execution_count": null,
85+
"outputs":

selfagent/.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Expo
5+
.expo/
6+
dist/
7+
web-build/
8+
9+
# Native builds
10+
android/
11+
ios/
12+
*.apk
13+
*.aab
14+
*.ipa
15+
16+
# Environment
17+
.env
18+
.env.local
19+
.env.production
20+
21+
# IDE
22+
.idea/
23+
.vscode/
24+
*.swp
25+
*.swo
26+
27+
# OS
28+
.DS_Store
29+
Thumbs.db
30+
31+
# Logs
32+
npm-debug.log*
33+
yarn-debug.log*
34+
yarn-error.log*
35+
36+
# Testing
37+
coverage/

selfagent/assets/.gitkeep

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# 请在此文件夹放置以下图片资源:
2+
# - icon.png (1024x1024) - 应用图标
3+
# - splash.png (1284x2778) - 启动画面
4+
# - adaptive-icon.png (1024x1024) - Android 自适应图标
5+
# - notification-icon.png (96x96) - 通知图标

selfagent/build_colab.ipynb

Lines changed: 193 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"metadata": {},
4040
"source": [
4141
"# 2. 安装 Java、Android SDK 和构建工具\n",
42-
"!sudo apt-get install -y openjdk-17-jdk wget unzip ninja-build cmake\n",
42+
"!sudo apt-get install -y openjdk-17-jdk wget unzip\n",
4343
"\n",
4444
"import os\n",
4545
"os.environ['JAVA_HOME'] = '/usr/lib/jvm/java-17-openjdk-amd64'\n",
@@ -55,7 +55,7 @@
5555
"os.environ['PATH'] = os.environ['ANDROID_HOME'] + '/cmdline-tools/latest/bin:' + os.environ['ANDROID_HOME'] + '/platform-tools:' + os.environ['PATH']\n",
5656
"\n",
5757
"!yes | ~/android-sdk/cmdline-tools/latest/bin/sdkmanager --licenses\n",
58-
"!~/android-sdk/cmdline-tools/latest/bin/sdkmanager \"platform-tools\" \"platforms;android-34\" \"build-tools;34.0.0\" \"cmake;3.22.1\" \"ndk;25.1.8937393\"\n",
58+
"!~/android-sdk/cmdline-tools/latest/bin/sdkmanager \"platform-tools\" \"platforms;android-34\" \"build-tools;34.0.0\"\n",
5959
"print(\"✅ Android SDK 安装完成\")"
6060
],
6161
"execution_count": null,
@@ -65,7 +65,7 @@
6565
"cell_type": "code",
6666
"metadata": {},
6767
"source": [
68-
"# 3. 克隆项目\n",
68+
"# 3. 克隆项目并安装依赖\n",
6969
"!git clone https://github.com/Michael-YuQ/python-calculator-android.git\n",
7070
"%cd python-calculator-android/selfagent\n",
7171
"!npm install\n",
@@ -91,14 +91,128 @@
9191
"cell_type": "code",
9292
"metadata": {},
9393
"source": [
94-
"# 5. 构建 APK\n",
94+
"# 5. 手动打包 JS Bundle (关键步骤!)\n",
95+
"!mkdir -p android/app/src/main/assets\n",
96+
"\n",
97+
"# 先清理可能存在的旧 bundle\n",
98+
"!rm -f android/app/src/main/assets/index.android.bundle\n",
99+
"\n",
100+
"# 使用 npx expo export 生成 bundle (更可靠)\n",
101+
"!npx expo export --platform android --output-dir dist\n",
102+
"\n",
103+
"# 复制生成的 bundle 到正确位置\n",
104+
"!cp dist/bundles/android-*.js android/app/src/main/assets/index.android.bundle 2>/dev/null || \\\n",
105+
" cp dist/_expo/static/js/android/*.js android/app/src/main/assets/index.android.bundle 2>/dev/null || \\\n",
106+
" echo '尝试使用 react-native bundle...'\n",
107+
"\n",
108+
"# 如果 expo export 失败,使用 react-native bundle 作为备选\n",
109+
"import os\n",
110+
"bundle_path = 'android/app/src/main/assets/index.android.bundle'\n",
111+
"if not os.path.exists(bundle_path) or os.path.getsize(bundle_path) < 1000:\n",
112+
" print('使用 react-native bundle 重新生成...')\n",
113+
" os.system('''npx react-native bundle \\\n",
114+
" --platform android \\\n",
115+
" --dev false \\\n",
116+
" --entry-file index.js \\\n",
117+
" --bundle-output android/app/src/main/assets/index.android.bundle \\\n",
118+
" --assets-dest android/app/src/main/res \\\n",
119+
" --reset-cache \\\n",
120+
" --minify true''')\n",
121+
"\n",
122+
"# 验证 bundle 文件\n",
123+
"if os.path.exists(bundle_path):\n",
124+
" size = os.path.getsize(bundle_path)\n",
125+
" print(f\"✅ JS Bundle 已生成: {size/1024:.1f} KB\")\n",
126+
" if size < 10000:\n",
127+
" print(\"⚠️ 警告: Bundle 文件太小,可能有问题\")\n",
128+
" # 显示 bundle 前几行确认内容\n",
129+
" with open(bundle_path, 'r', errors='ignore') as f:\n",
130+
" preview = f.read(200)\n",
131+
" print(f\"Bundle 预览: {preview[:100]}...\")\n",
132+
"else:\n",
133+
" print(\"❌ Bundle 生成失败\")\n",
134+
" exit(1)"
135+
],
136+
"execution_count": null,
137+
"outputs": []
138+
},
139+
{
140+
"cell_type": "code",
141+
"metadata": {},
142+
"source": [
143+
"# 6. 修改 build.gradle 确保 bundle 被包含\n",
144+
"build_gradle = 'android/app/build.gradle'\n",
145+
"\n",
146+
"with open(build_gradle, 'r') as f:\n",
147+
" content = f.read()\n",
148+
"\n",
149+
"# 确保 assets 目录被包含在构建中\n",
150+
"if 'sourceSets' not in content or 'assets.srcDirs' not in content:\n",
151+
" # 在 android { 块内添加 sourceSets 配置\n",
152+
" insert_point = content.find('android {')\n",
153+
" if insert_point > 0:\n",
154+
" # 找到 android { 后的第一个 { 对应的位置\n",
155+
" brace_count = 0\n",
156+
" for i, c in enumerate(content[insert_point:]):\n",
157+
" if c == '{':\n",
158+
" brace_count += 1\n",
159+
" if brace_count == 1:\n",
160+
" insert_idx = insert_point + i + 1\n",
161+
" break\n",
162+
" \n",
163+
" sourceSets_config = '''\n",
164+
" sourceSets {\n",
165+
" main {\n",
166+
" assets.srcDirs = ['src/main/assets']\n",
167+
" }\n",
168+
" }\n",
169+
"'''\n",
170+
" content = content[:insert_idx] + sourceSets_config + content[insert_idx:]\n",
171+
" with open(build_gradle, 'w') as f:\n",
172+
" f.write(content)\n",
173+
" print(\"✅ build.gradle 已添加 sourceSets 配置\")\n",
174+
"else:\n",
175+
" print(\"✅ sourceSets 配置已存在\")\n",
176+
"\n",
177+
"# 列出 assets 目录内容确认\n",
178+
"!ls -la android/app/src/main/assets/"
179+
],
180+
"execution_count": null,
181+
"outputs": []
182+
},
183+
{
184+
"cell_type": "code",
185+
"metadata": {},
186+
"source": [
187+
"# 7. 构建 Release APK\n",
95188
"import os\n",
96189
"os.environ['ANDROID_HOME'] = os.path.expanduser('~/android-sdk')\n",
97190
"os.environ['ANDROID_SDK_ROOT'] = os.environ['ANDROID_HOME']\n",
98191
"\n",
192+
"# 再次确认 bundle 存在\n",
193+
"bundle_check = '../android/app/src/main/assets/index.android.bundle'\n",
194+
"if not os.path.exists(bundle_check.replace('../', '')):\n",
195+
" print('⚠️ Bundle 不存在,重新生成...')\n",
196+
" os.chdir('..')\n",
197+
" os.system('mkdir -p android/app/src/main/assets')\n",
198+
" os.system('''npx react-native bundle \\\n",
199+
" --platform android \\\n",
200+
" --dev false \\\n",
201+
" --entry-file index.js \\\n",
202+
" --bundle-output android/app/src/main/assets/index.android.bundle \\\n",
203+
" --assets-dest android/app/src/main/res''')\n",
204+
" os.chdir('android')\n",
205+
"\n",
99206
"%cd android\n",
100207
"!chmod +x gradlew\n",
101-
"!./gradlew assembleDebug --no-daemon\n",
208+
"\n",
209+
"# 列出 assets 目录确认\n",
210+
"print('Assets 目录内容:')\n",
211+
"!ls -la app/src/main/assets/\n",
212+
"\n",
213+
"# 清理并构建,跳过 JS bundle 任务因为我们已经手动生成了\n",
214+
"!./gradlew clean\n",
215+
"!./gradlew assembleRelease -x bundleReleaseJsAndAssets --no-daemon --stacktrace\n",
102216
"print(\"✅ APK 构建完成\")"
103217
],
104218
"execution_count": null,
@@ -108,13 +222,83 @@
108222
"cell_type": "code",
109223
"metadata": {},
110224
"source": [
111-
"# 6. 下载 APK\n",
225+
"# 8. 验证并修复 APK 中的 bundle\n",
226+
"import os\n",
227+
"import subprocess\n",
228+
"\n",
229+
"apk_path = 'app/build/outputs/apk/release/app-release.apk'\n",
230+
"bundle_src = 'app/src/main/assets/index.android.bundle'\n",
231+
"\n",
232+
"if os.path.exists(apk_path):\n",
233+
" print(\"APK 内容检查:\")\n",
234+
" result = subprocess.run(['unzip', '-l', apk_path], capture_output=True, text=True)\n",
235+
" \n",
236+
" has_bundle = 'index.android.bundle' in result.stdout\n",
237+
" \n",
238+
" if has_bundle:\n",
239+
" print(\"✅ Bundle 已正确打包到 APK 中!\")\n",
240+
" # 显示 bundle 在 APK 中的信息\n",
241+
" for line in result.stdout.split('\\n'):\n",
242+
" if 'index.android.bundle' in line or 'assets' in line:\n",
243+
" print(line)\n",
244+
" else:\n",
245+
" print(\"❌ APK 中没有找到 bundle,手动添加...\")\n",
246+
" \n",
247+
" # 解压 APK\n",
248+
" !rm -rf temp_apk\n",
249+
" !mkdir -p temp_apk\n",
250+
" !unzip -q {apk_path} -d temp_apk\n",
251+
" \n",
252+
" # 添加 bundle\n",
253+
" !mkdir -p temp_apk/assets\n",
254+
" !cp {bundle_src} temp_apk/assets/index.android.bundle\n",
255+
" \n",
256+
" # 重新打包 (不签名的 APK)\n",
257+
" !rm -f app-release-fixed.apk\n",
258+
" os.chdir('temp_apk')\n",
259+
" !zip -r ../app-release-fixed-unsigned.apk .\n",
260+
" os.chdir('..')\n",
261+
" \n",
262+
" # 使用 zipalign 和 apksigner 签名\n",
263+
" # 生成一个临时 keystore\n",
264+
" !keytool -genkey -v -keystore temp.keystore -alias temp -keyalg RSA -keysize 2048 -validity 10000 -storepass android -keypass android -dname \"CN=Temp, OU=Temp, O=Temp, L=Temp, ST=Temp, C=US\" 2>/dev/null || true\n",
265+
" \n",
266+
" # zipalign\n",
267+
" !~/android-sdk/build-tools/34.0.0/zipalign -v 4 app-release-fixed-unsigned.apk app-release-aligned.apk\n",
268+
" \n",
269+
" # 签名\n",
270+
" !~/android-sdk/build-tools/34.0.0/apksigner sign --ks temp.keystore --ks-pass pass:android --key-pass pass:android --out app-release-fixed.apk app-release-aligned.apk\n",
271+
" \n",
272+
" # 清理\n",
273+
" !rm -rf temp_apk app-release-fixed-unsigned.apk app-release-aligned.apk temp.keystore\n",
274+
" \n",
275+
" if os.path.exists('app-release-fixed.apk'):\n",
276+
" print(\"✅ 已创建修复后的 APK: app-release-fixed.apk\")\n",
277+
" # 验证修复后的 APK\n",
278+
" !unzip -l app-release-fixed.apk | grep -E 'index.android.bundle|assets'\n",
279+
" else:\n",
280+
" print(\"❌ 修复失败\")\n",
281+
"else:\n",
282+
" print(\"❌ APK 未找到\")\n",
283+
" !find . -name \"*.apk\" -type f"
284+
],
285+
"execution_count": null,
286+
"outputs": []
287+
},
288+
{
289+
"cell_type": "code",
290+
"metadata": {},
291+
"source": [
292+
"# 9. 下载 APK\n",
112293
"from google.colab import files\n",
113294
"import shutil, os\n",
114295
"\n",
115-
"apk_path = 'app/build/outputs/apk/debug/app-debug.apk'\n",
116-
"if os.path.exists(apk_path):\n",
117-
" shutil.copy(apk_path, 'SelfAgent.apk')\n",
296+
"# 优先使用修复后的 APK\n",
297+
"if os.path.exists('app-release-fixed.apk'):\n",
298+
" files.download('app-release-fixed.apk')\n",
299+
" print(\"✅ 修复后的 APK 已下载\")\n",
300+
"elif os.path.exists('app/build/outputs/apk/release/app-release.apk'):\n",
301+
" shutil.copy('app/build/outputs/apk/release/app-release.apk', 'SelfAgent.apk')\n",
118302
" files.download('SelfAgent.apk')\n",
119303
" print(\"✅ APK 已下载\")\n",
120304
"else:\n",

0 commit comments

Comments
 (0)