```
Improve VPN service stability and update V2Ray assets Enhanced the VPN service by refining error handling, adding retry logic, and improving resource cleanup. Consolidated V2Ray asset management logic, ensured compatibility with device architectures, and adjusted permissions handling for newer Android versions. Renamed and reorganized V2Ray assets for better structure. ```
This commit is contained in:
parent
b46404bb1e
commit
c852142262
|
@ -4,10 +4,10 @@
|
||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2025-05-23T10:42:56.974007600Z">
|
<DropdownSelection timestamp="2025-05-26T09:42:51.679223700Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=89NX0C56X" />
|
<DeviceId pluginId="Default" identifier="serial=10.255.31.151:5555;connection=cafb3fe2" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|
|
@ -5,16 +5,14 @@
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.BIND_VPN_SERVICE" />
|
<uses-permission android:name="android.permission.BIND_VPN_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permision.VPN_SERVICE" />
|
<uses-permission android:name="android.permission.VPN_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
|
<uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,6 +43,7 @@
|
||||||
</activity>
|
</activity>
|
||||||
<service
|
<service
|
||||||
android:name=".proxy.CustomVpnService"
|
android:name=".proxy.CustomVpnService"
|
||||||
|
|
||||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
|
@ -45,6 +45,9 @@ import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
private static final int REQUEST_CODE_STORAGE_PERMISSION = 1;
|
private static final int REQUEST_CODE_STORAGE_PERMISSION = 1;
|
||||||
private static final int VPN_REQUEST_CODE = 100; // Adding the missing constant
|
private static final int VPN_REQUEST_CODE = 100; // Adding the missing constant
|
||||||
|
|
||||||
|
private static final int ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE = 1001;
|
||||||
|
|
||||||
private static final int REQUEST_CODE_VPN = 2;
|
private static final int REQUEST_CODE_VPN = 2;
|
||||||
|
|
||||||
private BroadcastReceiver scriptResultReceiver;
|
private BroadcastReceiver scriptResultReceiver;
|
||||||
|
@ -52,23 +55,35 @@ public class MainActivity extends AppCompatActivity {
|
||||||
private ActivityResultLauncher<Intent> vpnRequestLauncher;
|
private ActivityResultLauncher<Intent> vpnRequestLauncher;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
// 检查存储权限
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
// 针对 Android 10 或更低版本检查普通存储权限
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
ActivityCompat.requestPermissions(this,
|
!= PackageManager.PERMISSION_GRANTED
|
||||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
) {
|
||||||
REQUEST_CODE_STORAGE_PERMISSION);
|
ActivityCompat.requestPermissions(
|
||||||
|
this,
|
||||||
|
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||||
|
REQUEST_CODE_STORAGE_PERMISSION
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 针对 Android 11 及更高版本检查全文件管理权限
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
|
||||||
|
// 请求权限
|
||||||
|
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
|
||||||
|
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||||
|
startActivityForResult(intent, ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化按钮
|
||||||
// 查找按钮对象
|
|
||||||
Button runScriptButton = findViewById(R.id.run_script_button);
|
Button runScriptButton = findViewById(R.id.run_script_button);
|
||||||
if (runScriptButton != null) {
|
if (runScriptButton != null) {
|
||||||
runScriptButton.setOnClickListener(view -> runAutojsScript()); // 设置点击事件
|
runScriptButton.setOnClickListener(v -> runAutojsScript());
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, "Button not found", Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, "Button not found", Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
@ -137,6 +152,20 @@ public class MainActivity extends AppCompatActivity {
|
||||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
|
if (requestCode == ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
|
||||||
|
if (!isNetworkAvailable(this)) {
|
||||||
|
Toast.makeText(this, "Network is not available", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 启动 VPN 服务
|
||||||
|
startProxyVpn(this);
|
||||||
|
} else {
|
||||||
|
// 权限未授予,可提示用户
|
||||||
|
Toast.makeText(this, "请授予所有文件管理权限", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (requestCode == VPN_REQUEST_CODE && resultCode == RESULT_OK) {
|
if (requestCode == VPN_REQUEST_CODE && resultCode == RESULT_OK) {
|
||||||
// Permission granted, now you can start your VpnService
|
// Permission granted, now you can start your VpnService
|
||||||
Intent intent = new Intent(this, CustomVpnService.class);
|
Intent intent = new Intent(this, CustomVpnService.class);
|
||||||
|
@ -150,10 +179,6 @@ public class MainActivity extends AppCompatActivity {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
showToastOnUiThread(this, "Failed to start VPN service");
|
showToastOnUiThread(this, "Failed to start VPN service");
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Permission denied or an error occurred
|
|
||||||
Log.e("VPNSetup", "VPN permission denied or cancelled by user.");
|
|
||||||
// Handle denial: show a message to the user, disable VPN functionality, etc.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,7 @@ public class CustomVpnService extends VpnService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder.setBlocking(true);
|
||||||
// 直接建立 TUN 虚拟接口
|
// 直接建立 TUN 虚拟接口
|
||||||
vpnInterface = builder.establish();
|
vpnInterface = builder.establish();
|
||||||
if (vpnInterface == null) {
|
if (vpnInterface == null) {
|
||||||
|
@ -160,62 +161,76 @@ public class CustomVpnService extends VpnService {
|
||||||
return dnsServers;
|
return dnsServers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
try {
|
if (vpnInterface != null) {
|
||||||
if (vpnInterface != null) {
|
try {
|
||||||
vpnInterface.close();
|
vpnInterface.close();
|
||||||
|
vpnInterface = null;
|
||||||
|
Log.d("CustomVpnService", "VPN interface closed.");
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e("CustomVpnService", "Error closing VPN interface", e);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void handleVpnTraffic(ParcelFileDescriptor vpnInterface) {
|
private void handleVpnTraffic(ParcelFileDescriptor vpnInterface) {
|
||||||
if (vpnInterface == null || !vpnInterface.getFileDescriptor().valid()) {
|
if (vpnInterface == null || !vpnInterface.getFileDescriptor().valid()) {
|
||||||
throw new IllegalArgumentException("ParcelFileDescriptor is invalid!");
|
throw new IllegalArgumentException("ParcelFileDescriptor is invalid!");
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] packetData = new byte[32767];
|
byte[] packetData = new byte[32767];
|
||||||
|
final int maxRetry = 5; // 定义最大重试次数
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
try (FileInputStream inStream = new FileInputStream(vpnInterface.getFileDescriptor());
|
|
||||||
FileOutputStream outStream = new FileOutputStream(vpnInterface.getFileDescriptor())) {
|
|
||||||
|
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
FileInputStream inStream = new FileInputStream(vpnInterface.getFileDescriptor());
|
||||||
try {
|
FileOutputStream outStream = new FileOutputStream(vpnInterface.getFileDescriptor());
|
||||||
int length = inStream.read(packetData);
|
// 将流放在 try-with-resources 之外,避免循环中被关闭
|
||||||
if (length == -1) {
|
|
||||||
// EOF
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length > 0) {
|
// 循环处理 VPN 数据包
|
||||||
boolean handled = processPacket(packetData, length);
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
if (!handled) {
|
try {
|
||||||
outStream.write(packetData, 0, length);
|
int length = inStream.read(packetData);
|
||||||
}
|
if (length == -1) {
|
||||||
}
|
// 读取结束
|
||||||
retryCount = 0; // Reset retry count after successful read
|
break;
|
||||||
} catch (IOException e) {
|
}
|
||||||
retryCount++;
|
|
||||||
Log.e("CustomVpnService", "Error reading packet. Retry attempt " + retryCount, e);
|
if (length > 0) {
|
||||||
if (retryCount >= MAX_RETRY) { // Add constant definition
|
boolean handled = processPacket(packetData, length);
|
||||||
Log.e("CustomVpnService", "Max retry reached. Exiting loop.");
|
if (!handled) {
|
||||||
break;
|
outStream.write(packetData, 0, length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
retryCount = 0; // 成功一次后重置重试次数
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e("CustomVpnService", "IO error in handleVpnTraffic", e);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
vpnInterface.close();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e("CustomVpnService", "Failed to close vpnInterface", e);
|
retryCount++;
|
||||||
|
Log.e("CustomVpnService", "Error reading packet. Retry attempt " + retryCount, e);
|
||||||
|
if (retryCount >= maxRetry) {
|
||||||
|
Log.e("CustomVpnService", "Max retry reached. Exiting loop.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 可添加短暂延迟来避免频繁重试
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
Log.e("CustomVpnService", "Thread interrupted during sleep", ie);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 最终关闭 ParcelFileDescriptor
|
||||||
|
try {
|
||||||
|
vpnInterface.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e("CustomVpnService", "Failed to close vpnInterface", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean processPacket(byte[] packetData, int length) {
|
private boolean processPacket(byte[] packetData, int length) {
|
||||||
if (packetData == null || length <= 0 || length > packetData.length) {
|
if (packetData == null || length <= 0 || length > packetData.length) {
|
||||||
Log.w("CustomVpnService", "Invalid packetData or length");
|
Log.w("CustomVpnService", "Invalid packetData or length");
|
||||||
|
@ -268,6 +283,4 @@ public class CustomVpnService extends VpnService {
|
||||||
|
|
||||||
// 检查是否是 UDP 的 DNS 端口 (53)
|
// 检查是否是 UDP 的 DNS 端口 (53)
|
||||||
return sourcePort == 53 || destPort == 53;
|
return sourcePort == 53 || destPort == 53;
|
||||||
}
|
}}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,105 +1,114 @@
|
||||||
package com.example.studyapp.utils;
|
package com.example.studyapp.utils;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.example.studyapp.MainActivity;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class V2rayUtil {
|
public class V2rayUtil {
|
||||||
|
|
||||||
public static void startV2Ray(Context context) {
|
public static void startV2Ray(Context context) {
|
||||||
// 确保文件存在
|
|
||||||
ensureV2RayFilesExist(context);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取文件路径
|
// 确保文件存在并准备就绪
|
||||||
File fileDir = context.getFilesDir();
|
if (!ensureV2RayFilesExist(context)) {
|
||||||
File v2rayBinary = new File(fileDir, "v2ray/v2ray");
|
Log.e("V2Ray", "V2Ray files are missing, cannot start.");
|
||||||
File v2rayConfig = new File(fileDir, "v2ray/config.json");
|
|
||||||
|
|
||||||
// 检查文件存在性(再次验证)
|
|
||||||
if (!v2rayBinary.exists() || !v2rayConfig.exists()) {
|
|
||||||
Log.e("V2Ray", "V2Ray binary or config file not found");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查权限
|
// 获取文件路径
|
||||||
if (!v2rayBinary.setExecutable(true)) {
|
File v2rayBinary = new File(context.getCodeCacheDir(), "v2ray");
|
||||||
throw new IllegalStateException("Failed to make V2Ray binary executable");
|
File v2rayConfig = new File(context.getCodeCacheDir(), "config.json");
|
||||||
}
|
|
||||||
|
|
||||||
// 构建命令
|
// 构建命令
|
||||||
ProcessBuilder builder = new ProcessBuilder()
|
ProcessBuilder builder = new ProcessBuilder(v2rayBinary.getAbsolutePath(), "-config", v2rayConfig.getAbsolutePath()).redirectErrorStream(true);
|
||||||
.command(v2rayBinary.getAbsolutePath(),
|
|
||||||
"-config",
|
|
||||||
v2rayConfig.getAbsolutePath())
|
|
||||||
.directory(fileDir);
|
|
||||||
|
|
||||||
// 其余逻辑不变...
|
// 启动进程
|
||||||
Process process = builder.start();
|
try {
|
||||||
|
Process process = builder.start();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e("V2Ray", "Failed to start the process", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 捕获输出逻辑略...
|
// 日志输出
|
||||||
Log.i("V2Ray", "V2Ray service started");
|
Log.i("V2Ray", "V2Ray service started");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("V2Ray", "Failed to start V2Ray core", e);
|
Log.e("V2Ray", "Failed to start V2Ray core", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ensureV2RayFilesExist(Context context) {
|
public static boolean ensureV2RayFilesExist(Context context) {
|
||||||
File filesDir = context.getFilesDir();
|
|
||||||
File v2rayDir = new File(filesDir, "v2ray");
|
|
||||||
File v2rayBinary = new File(v2rayDir, "v2ray");
|
|
||||||
File v2rayConfig = new File(v2rayDir, "config.json");
|
|
||||||
|
|
||||||
try {
|
synchronized (V2rayUtil.class) {
|
||||||
// 创建 v2ray 目录
|
try {
|
||||||
if (!v2rayDir.exists() && v2rayDir.mkdirs()) {
|
// 检查并复制 v2ray 可执行文件
|
||||||
Log.i("V2Ray", "Created directory: " + v2rayDir.getAbsolutePath());
|
String abi = Build.SUPPORTED_ABIS[0]; // 获取当前设备支持的 ABI 架构
|
||||||
}
|
File binaryOutputFile = new File(context.getCodeCacheDir(), "v2ray");
|
||||||
|
|
||||||
// 检查并复制 v2ray 可执行文件
|
if (!binaryOutputFile.exists()) {
|
||||||
if (!v2rayBinary.exists()) {
|
InputStream binaryInputStream = context.getAssets().open("v2ray/" + abi + "/v2ray");
|
||||||
try (InputStream input = context.getAssets().open("v2ray/v2ray");
|
FileOutputStream binaryOutputStream = new FileOutputStream(binaryOutputFile);
|
||||||
FileOutputStream output = new FileOutputStream(v2rayBinary)) {
|
try {
|
||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[1024];
|
||||||
int length;
|
int length;
|
||||||
while ((length = input.read(buffer)) > 0) {
|
while ((length = binaryInputStream.read(buffer)) > 0) {
|
||||||
output.write(buffer, 0, length);
|
binaryOutputStream.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
Log.i("V2Ray", "Copied v2ray binary to: " + binaryOutputFile.getAbsolutePath());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("V2rayUtil", "Failed to copy v2ray binary", e);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
binaryInputStream.close();
|
||||||
|
binaryOutputStream.close();
|
||||||
}
|
}
|
||||||
Log.i("V2Ray", "Copied v2ray binary to: " + v2rayBinary.getAbsolutePath());
|
|
||||||
}
|
}
|
||||||
}
|
binaryOutputFile.setExecutable(true, false);
|
||||||
|
binaryOutputFile.setReadable(true, false);
|
||||||
|
binaryOutputFile.setWritable(true, false);
|
||||||
|
|
||||||
// 确保可执行权限
|
// 检查文件是否已经具有可执行权限
|
||||||
if (!v2rayBinary.setExecutable(true)) {
|
if (!binaryOutputFile.canExecute()) {
|
||||||
throw new IllegalStateException("Failed to make v2ray binary executable");
|
Log.e("V2rayUtil", "Binary file does not have execute permission. Aborting start.");
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 检查并复制 config.json 配置文件
|
// 检查并复制 config.json 文件
|
||||||
if (!v2rayConfig.exists()) {
|
|
||||||
try (InputStream input = context.getAssets().open("v2ray/config.json");
|
|
||||||
FileOutputStream output = new FileOutputStream(v2rayConfig)) {
|
File configFile = new File(context.getCodeCacheDir(), "config.json");
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int length;
|
if (!configFile.exists()) {
|
||||||
while ((length = input.read(buffer)) > 0) {
|
InputStream configInputStream = context.getAssets().open("v2ray/" + abi + "/config.json");
|
||||||
output.write(buffer, 0, length);
|
FileOutputStream configOutputStream = new FileOutputStream(configFile);
|
||||||
|
try {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int length;
|
||||||
|
while ((length = configInputStream.read(buffer)) > 0) {
|
||||||
|
configOutputStream.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
Log.i("V2Ray", "Copied v2ray config.json to: " + configFile.getAbsolutePath());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("V2rayUtil", "Failed to copy config.json", e);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
configInputStream.close();
|
||||||
|
configOutputStream.close();
|
||||||
}
|
}
|
||||||
Log.i("V2Ray", "Copied v2ray config to: " + v2rayConfig.getAbsolutePath());
|
|
||||||
}
|
}
|
||||||
|
configFile.setReadable(true, false);
|
||||||
|
configFile.setWritable(true, false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e("V2Ray", "Failed to prepare V2Ray files", e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e("V2Ray", "Failed to prepare V2Ray files", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
V2243A:/ # ls -l /data/user/0/com.example.studyapp/files/
|
||||||
|
total 37516
|
||||||
|
-rw-rw-rw- 1 u0_a135 u0_a135 2398 2025-05-27 10:43 config.json
|
||||||
|
-rw------- 1 u0_a135 u0_a135 24 2025-05-27 10:42 profileInstalled
|
||||||
|
-rwxrwxrwx 1 u0_a135 u0_a135 38404413 2025-05-27 10:43 v2ray
|
||||||
|
V2243A:/ #
|
|
@ -0,0 +1,45 @@
|
||||||
|
2025-05-27 19:04:08.548 301-8923 Vpn system_server I Established by com.example.studyapp on tun0
|
||||||
|
2025-05-27 19:04:08.551 41957-42156 CustomVpnService com.example.studyapp D Packet Info: TCP=false, DNS=false, Length=76
|
||||||
|
2025-05-27 19:04:08.553 933-933 GoogleInpu...hodService com...gle.android.inputmethod.latin I GoogleInputMethodService.onStartInput():1247 onStartInput(EditorInfo{EditorInfo{packageName=com.example.studyapp, inputType=0, inputTypeString=NULL, enableLearning=false, autoCorrection=false, autoComplete=false, imeOptions=0, privateImeOptions=null, actionName=UNSPECIFIED, actionLabel=null, initialSelStart=-1, initialSelEnd=-1, initialCapsMode=0, label=null, fieldId=-1, fieldName=null, extras=null, hintText=null, hintLocales=[]}}, false)
|
||||||
|
2025-05-27 19:04:08.557 41957-42156 CustomVpnService com.example.studyapp E Error reading packet. Retry attempt 1 (Ask Gemini)
|
||||||
|
java.io.IOException: Stream Closed
|
||||||
|
at java.io.FileInputStream.read(FileInputStream.java:316)
|
||||||
|
at java.io.FileInputStream.read(FileInputStream.java:292)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService.handleVpnTraffic(CustomVpnService.java:192)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService.lambda$startVpn$0$com-example-studyapp-proxy-CustomVpnService(CustomVpnService.java:113)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
|
||||||
|
at java.lang.Thread.run(Thread.java:1012)
|
||||||
|
2025-05-27 19:04:08.557 41957-42156 CustomVpnService com.example.studyapp E Error reading packet. Retry attempt 2 (Ask Gemini)
|
||||||
|
java.io.IOException: Stream Closed
|
||||||
|
at java.io.FileInputStream.read(FileInputStream.java:316)
|
||||||
|
at java.io.FileInputStream.read(FileInputStream.java:292)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService.handleVpnTraffic(CustomVpnService.java:192)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService.lambda$startVpn$0$com-example-studyapp-proxy-CustomVpnService(CustomVpnService.java:113)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
|
||||||
|
at java.lang.Thread.run(Thread.java:1012)
|
||||||
|
2025-05-27 19:04:08.557 41957-42156 CustomVpnService com.example.studyapp E Error reading packet. Retry attempt 3 (Ask Gemini)
|
||||||
|
java.io.IOException: Stream Closed
|
||||||
|
at java.io.FileInputStream.read(FileInputStream.java:316)
|
||||||
|
at java.io.FileInputStream.read(FileInputStream.java:292)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService.handleVpnTraffic(CustomVpnService.java:192)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService.lambda$startVpn$0$com-example-studyapp-proxy-CustomVpnService(CustomVpnService.java:113)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
|
||||||
|
at java.lang.Thread.run(Thread.java:1012)
|
||||||
|
2025-05-27 19:04:08.558 41957-42156 CustomVpnService com.example.studyapp E Error reading packet. Retry attempt 4 (Ask Gemini)
|
||||||
|
java.io.IOException: Stream Closed
|
||||||
|
at java.io.FileInputStream.read(FileInputStream.java:316)
|
||||||
|
at java.io.FileInputStream.read(FileInputStream.java:292)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService.handleVpnTraffic(CustomVpnService.java:192)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService.lambda$startVpn$0$com-example-studyapp-proxy-CustomVpnService(CustomVpnService.java:113)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
|
||||||
|
at java.lang.Thread.run(Thread.java:1012)
|
||||||
|
2025-05-27 19:04:08.558 41957-42156 CustomVpnService com.example.studyapp E Error reading packet. Retry attempt 5 (Ask Gemini)
|
||||||
|
java.io.IOException: Stream Closed
|
||||||
|
at java.io.FileInputStream.read(FileInputStream.java:316)
|
||||||
|
at java.io.FileInputStream.read(FileInputStream.java:292)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService.handleVpnTraffic(CustomVpnService.java:192)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService.lambda$startVpn$0$com-example-studyapp-proxy-CustomVpnService(CustomVpnService.java:113)
|
||||||
|
at com.example.studyapp.proxy.CustomVpnService$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
|
||||||
|
at java.lang.Thread.run(Thread.java:1012)
|
||||||
|
2025-05-27 19:04:08.558 41957-42156 CustomVpnService com.example.studyapp E Max retry reached. Exiting loop.
|
||||||
|
2025-05-27 19:04:08.582 301-1568 WindowManager system_server V getPackagePerformanceMode -- ComponentInfo{com.example.studyapp/com.example.studyapp.MainActivity} -- com.example.studyapp -- mode=0
|
Loading…
Reference in New Issue