2025-7-12-尝试开发远古安卓软件

2025-7-12-尝试开发远古安卓软件

七月 12, 2025

因为有需求 所以有了这次尝试

由于种种原因(手机丢失… 卧室搬迁)

我的 Ubuntu服务器移动了位置 ,而位于原址的网络处于停用状态。

恢复使用的一大方案就是放置一台终端连接那边的网络,然后设置为DMZ主机。

但是很显然,我现在不具备这个条件。

所幸 2020年认识的 HLL(货拉拉)同学赠送了我一台 海信芯片(Hi3798MV310)的 UNT402H

此前尝试过获取ROOT 权限,刷入 Linux 或者启用一些终端之类的操作,均失败

今天我想到了绝佳方案

编译为 arm平台 的现成的 frps 完全有条件可以部署在这台设备上,然后我其他没有公网端口使用的设备向这里穿透就可以使用了。

需要考虑的问题

  • 停电的可能
  • 动态公网IP会重置
  • 无法直接操作此终端

解决方案

  • 开机自启动

  • 向固定域名发送ip地址,防止失联

大概折腾了五个小时

先尝试 adb 连接,测试了frps 确实可行

然后搞这个环境用了许久,Android Studio 我并不熟悉,算是踩了一点坑。

这些折腾完大概花了三小时

实际开发代码

MainActivity.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package com.curesky.netserver

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import com.curesky.netserver.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding
private val frpsBinName = "frps" // 可执行文件名
private val frpsConfigName = "frps.ini" // 配置文件

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

// 初始化FRPS文件
initFrpsFiles()

// 直接执行 FRPS(不再依赖按钮点击)
runFrps()

// 可选:列出文件(调试用)
listPrivateFiles()
}

/**
* 将assets中的frps文件复制到私有目录
*/
private fun initFrpsFiles() {
try {
// 1. 创建bin目录(/data/data/包名/files/bin)
val binDir = File(filesDir, "bin").apply {
if (!exists()) mkdirs()
}
// 2. 复制可执行文件
copyAssetToFile(frpsBinName, File(binDir, frpsBinName).apply {
// 添加执行权限(仅Linux有效)
setExecutable(true)
})
// 3. 复制配置文件
copyAssetToFile(frpsConfigName, File(filesDir, frpsConfigName))
} catch (e: Exception) {
binding.textView.text = "初始化FRPS失败: ${e.message}"
}
}

/**
* 从assets复制文件到目标位置
*/
private fun copyAssetToFile(assetName: String, targetFile: File) {
try {
assets.open(assetName).use { input ->
FileOutputStream(targetFile).use { output ->
input.copyTo(output)
}
}
} catch (e: IOException) {
throw IOException("复制 $assetName 失败", e)
}
}

/**
* 执行frps(需要处理权限问题)
*/
private fun runFrps() {
val frpsPath = File(filesDir, "bin/$frpsBinName").absolutePath
val configPath = File(filesDir, frpsConfigName).absolutePath
try {
// 执行命令
// binding.textView.append("\n\n🚀 [FRPS 服务]")

Runtime.getRuntime().exec("$frpsPath -c $configPath")
} catch (e: Exception) {
binding.textView.append("\n\n❌ FRPS启动失败: ${e.message}")
}
}

/**
* 列出私有目录文件(调试用)
*/
private fun listPrivateFiles() {
val sb = StringBuilder("📁 私有目录文件列表:\n\n")

// 列出所有目录
listFilesInDirectory(filesDir, sb)
// 显示结果
binding.textView.text = sb.toString()
}

private fun listFilesInDirectory(dir: File, sb: StringBuilder, indent: String = "") {
dir.listFiles()?.forEach { file ->
val prefix = if (file.isDirectory) "📂 " else "📄 "
sb.append("$indent$prefix${file.name}\n")
if (file.isDirectory) {
listFilesInDirectory(file, sb, "$indent ")
}
}
}
}

BootReceiver.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.curesky.netserver; // 确保包名与 manifest 一致

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
// 开机后启动 MainActivity
Intent launchIntent = new Intent(context, MainActivity.class);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 必须加!
context.startActivity(launchIntent);


}
}
}

AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.curesky.netserver">

<!-- 权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.NetServer">

<!-- MainActivity(入口) -->
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- 广播接收器:监听开机启动 -->
<receiver
android:name=".BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<!-- 可选:监听用户解锁设备(某些设备需要) -->
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>

</application>
</manifest>

以上三个内容基本是我开发的重点所在

两个核心逻辑实现,和一个权限实现

想总结些什么,但是身体还没调理好,所以仅能记录这些了。