feat(android): 增加独立Android原生离线工程与GitHub Actions自动化构建

- 原生工程物理隔离:新建 android/ 独立工程,零侵入 Web 端现有源码
- 自动化资产同步:通过 android/copy-web-assets.sh 在构建时复制 Web 静态资产
- 原生体验调优:沉浸式状态栏适配、明文HTTP与DOM存储支持、返回键防误退
- 高清图标生成:基于 icon-512.png 自动生成全套 mipmap 分辨率桌面图标
- 云端CI构建:添加 .github/workflows/build-apk.yml 支持一键/Tag打包输出 APK
This commit is contained in:
OpenClaw Agent
2026-09-15 01:36:35 +08:00
parent 53733b39fc
commit f605d26b2b
26 changed files with 699 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.WorkHours"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:configChanges="orientation|screenSize|keyboardHidden"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,149 @@
package top.ouaone.workhours;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private WebView webView;
private long lastBackPressTime = 0;
private static final long BACK_PRESS_INTERVAL = 2000; // 2秒双击防误退
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置状态栏与导航栏外观
setupSystemBars();
webView = new WebView(this);
webView.setBackgroundColor(Color.parseColor("#1E293B"));
setContentView(webView);
configureWebSettings();
setupClients();
setupBackNavigation();
// 加载打包在 assets 中的离线应用
webView.loadUrl("file:///android_asset/www/index.html");
}
private void setupSystemBars() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(Color.parseColor("#1E293B"));
getWindow().setNavigationBarColor(Color.parseColor("#F8FAFC"));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
View decor = getWindow().getDecorView();
int flags = decor.getSystemUiVisibility();
flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; // 浅色导航栏图标
decor.setSystemUiVisibility(flags);
}
}
@SuppressLint("SetJavaScriptEnabled")
private void configureWebSettings() {
WebSettings s = webView.getSettings();
s.setJavaScriptEnabled(true);
s.setDomStorageEnabled(true);
s.setDatabaseEnabled(true);
s.setAllowFileAccess(true);
s.setAllowContentAccess(true);
s.setAllowFileAccessFromFileURLs(true);
s.setAllowUniversalAccessFromFileURLs(true);
s.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
s.setUseWideViewPort(true);
s.setLoadWithOverviewMode(true);
s.setSupportZoom(false);
s.setDisplayZoomControls(false);
s.setCacheMode(WebSettings.LOAD_DEFAULT);
}
private void setupClients() {
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
Uri uri = request.getUrl();
String scheme = uri.getScheme();
if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) {
// 仅当点击外部链接且不是同步 API 时调用系统浏览器
String url = uri.toString();
if (!url.contains("/api/")) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
return true;
} catch (Exception ignored) {
}
}
}
return false;
}
});
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onConsoleMessage(ConsoleMessage cm) {
// 控制台日志过滤输出到 Logcat
android.util.Log.d("WorkHoursWebView", cm.message() + " -- From line "
+ cm.lineNumber() + " of " + cm.sourceId());
return true;
}
});
}
private void setupBackNavigation() {
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
long now = System.currentTimeMillis();
if (now - lastBackPressTime < BACK_PRESS_INTERVAL) {
finish();
} else {
lastBackPressTime = now;
Toast.makeText(MainActivity.this, "再按一次退出工时记账", Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
protected void onResume() {
super.onResume();
if (webView != null) {
webView.onResume();
}
}
@Override
protected void onPause() {
if (webView != null) {
webView.onPause();
}
super.onPause();
}
@Override
protected void onDestroy() {
if (webView != null) {
webView.destroy();
}
super.onDestroy();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primary">#4F46E5</color>
<color name="status_bar_color">#1E293B</color>
<color name="nav_bar_color">#F8FAFC</color>
<color name="window_background">#1E293B</color>
</resources>
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">工时记账</string>
</resources>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.WorkHours" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:statusBarColor">@color/status_bar_color</item>
<item name="android:navigationBarColor">@color/nav_bar_color</item>
<item name="android:windowBackground">@color/window_background</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowLightNavigationBar">true</item>
</style>
</resources>