FLutter 安卓Android防抓包检测

一、前言

最近在写一个APP,目前已经写差不多了,需要用到防抓包检测,研究了一下捣鼓出来了,期间踩了很多坑,特此记录一下,大佬勿喷。

二、方法步骤

1.因为代理检测需要用到原生方法,所以需要用到kotlin,找到以下路径,新增一个文件 NetworkProxyPlugin.kt
D:\Flutter\demo_1\android\app\src\main\kotlin\com\example\demo_1
2.文件完整内容如下
package com.example.demo_1

import android.content.Context
import android.net.Proxy
import android.os.Build
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.lang.reflect.Method

class NetworkProxyPlugin: FlutterPlugin, MethodCallHandler {
    private lateinit var channel : MethodChannel
    private lateinit var context: Context

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "network_proxy")
        channel.setMethodCallHandler(this)
        context = flutterPluginBinding.applicationContext

        // 注册系统属性通道
        val systemPropertiesChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "system_properties")
        systemPropertiesChannel.setMethodCallHandler { call, result ->
            if (call.method == "getProperty") {
                val propertyName = call.argument<String>("name") ?: ""
                result.success(getSystemProperty(propertyName))
            } else {
                result.notImplemented()
            }
        }
    }

    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        if (call.method == "getProxy") {
            result.success(getProxySettings())
        } else {
            result.notImplemented()
        }
    }

    @Suppress("DEPRECATION")
    private fun getProxySettings(): String {
        return try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                // 使用反射调用 ProxyInfo.getDefaultProxy() 方法
                val proxyInfo = getDefaultProxyUsingReflection()
                if (proxyInfo != null) {
                    val host = getProxyHost(proxyInfo)
                    val port = getProxyPort(proxyInfo)
                    if (host != null && port != null) {
                        "$host:$port"
                    } else {
                        "DIRECT"
                    }
                } else {
                    "DIRECT"
                }
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                // Android 4.0 - 9 使用 System.getProperty
                val host = System.getProperty("http.proxyHost") ?: ""
                val port = System.getProperty("http.proxyPort") ?: ""

                if (host.isNotEmpty() && port.isNotEmpty()) {
                    "$host:$port"
                } else {
                    "DIRECT"
                }
            } else {
                // Android 4.0 以下使用旧的 android.net.Proxy 类
                val host = Proxy.getHost(context) ?: ""
                val port = Proxy.getPort(context)

                if (host.isNotEmpty() && port != -1) {
                    "$host:$port"
                } else {
                    "DIRECT"
                }
            }
        } catch (e: Exception) {
            "DIRECT"
        }
    }

    // 使用反射调用 ProxyInfo.getDefaultProxy()
    private fun getDefaultProxyUsingReflection(): Any? {
        return try {
            val proxyInfoClass = Class.forName("android.net.ProxyInfo")
            val getDefaultProxyMethod: Method = proxyInfoClass.getMethod("getDefaultProxy")
            getDefaultProxyMethod.invoke(null)
        } catch (e: Exception) {
            null
        }
    }

    // 使用反射获取代理主机名
    private fun getProxyHost(proxyInfo: Any): String? {
        return try {
            val getHostMethod: Method = proxyInfo.javaClass.getMethod("getHost")
            getHostMethod.invoke(proxyInfo) as String?
        } catch (e: Exception) {
            null
        }
    }

    // 使用反射获取代理端口
    private fun getProxyPort(proxyInfo: Any): Int? {
        return try {
            val getPortMethod: Method = proxyInfo.javaClass.getMethod("getPort")
            getPortMethod.invoke(proxyInfo) as Int?
        } catch (e: Exception) {
            null
        }
    }

    private fun getSystemProperty(propertyName: String): String? {
        return try {
            System.getProperty(propertyName)
        } catch (e: Exception) {
            null
        }
    }

    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }
}
3.配置MainActivity.kt内容
package com.example.demo_1

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        // 注册网络代理插件
        flutterEngine.plugins.add(NetworkProxyPlugin())
    }
}
4.需要引入一个插件 在 pubspec.yaml 中引入 connectivity_plus: ^6.1.4
5.完整例子以及代码展示
import 'dart:io';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class NetworkUtils {
  // 检测是否使用WiFi代理
  static Future<bool> isWifiProxy() async {
    var connectivityResult = await (Connectivity().checkConnectivity());
    // 确保设备连接到WiFi
    /*if (connectivityResult != ConnectivityResult.wifi) {
      return false;
    }*/

    try {
      if (Platform.isAndroid) {
        return await _checkAndroidProxy();
      }
      return false;
    } catch (e) {
      print('检测WiFi代理时出错: $e');
      return false;
    }
  }

  // 检测Android代理
  static Future<bool> _checkAndroidProxy() async {
    print("_checkAndroidProxy");
    try {
      // 尝试通过MethodChannel获取代理设置
      const platform = MethodChannel('network_proxy');
      final proxy = await platform.invokeMethod('getProxy');
      return proxy.isNotEmpty && proxy != 'DIRECT';
    } catch (e) {
      print('通过MethodChannel获取代理失败,尝试其他方法: $e');
      // 备选方案:尝试通过系统属性获取
      try {
        final String? javaClass = 'android.net.Proxy';
        final String? host = await _getSystemProperty('http.proxyHost');
        final String? port = await _getSystemProperty('http.proxyPort');

        if (host != null &&
            host.isNotEmpty &&
            port != null &&
            port.isNotEmpty) {
          print('检测到代理: $host:$port');
          return true;
        }
        return false;
      } catch (e) {
        print('通过系统属性获取代理失败: $e');
        // 最后手段:检查环境变量
        final httpProxy = Platform.environment['http_proxy'] ?? '';
        final httpsProxy = Platform.environment['https_proxy'] ?? '';
        return httpProxy.isNotEmpty || httpsProxy.isNotEmpty;
      }
    }
  }

  // 通过MethodChannel获取系统属性
  static Future<String?> _getSystemProperty(String propertyName) async {
    try {
      const platform = MethodChannel('system_properties');
      return await platform.invokeMethod('getProperty', {'name': propertyName});
    } catch (e) {
      print('获取系统属性失败: $e');
      return null;
    }
  }

  // 检测是否正在使用VPN
  static Future<bool> isVpnUsed() async {
    try {
      // 获取所有网络接口
      final interfaces = await NetworkInterface.list(
        includeLoopback: false,
        type: InternetAddressType.any,
      );

      // 检查是否存在VPN接口
      for (var interface in interfaces) {
        if (interface.name.contains('tun') ||
            interface.name.contains('ppp') ||
            interface.name.contains('ipsec') ||
            interface.name.contains('wg')) {
          return true;
        }
      }
    } catch (e) {
      print('检测VPN时出错: $e');
    }
    return false;
  }
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '网络检测示例',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const NetworkDetectionScreen(),
    );
  }
}

class NetworkDetectionScreen extends StatefulWidget {
  const NetworkDetectionScreen({Key? key}) : super(key: key);

  @override
  State<NetworkDetectionScreen> createState() => _NetworkDetectionScreenState();
}

class _NetworkDetectionScreenState extends State<NetworkDetectionScreen> {
  bool _isProxyUsed = false;
  bool _isVpnUsed = false;
  String _statusMessage = '点击按钮开始检测';
  bool _isChecking = false;

  Future<void> _checkNetworkStatus() async {
    setState(() {
      _isChecking = true;
      _statusMessage = '正在检测...';
    });

    try {
      final isProxy = await NetworkUtils.isWifiProxy();
      final isVpn = await NetworkUtils.isVpnUsed();

      setState(() {
        _isProxyUsed = isProxy;
        _isVpnUsed = isVpn;

        if (isProxy && isVpn) {
          _statusMessage = '检测到同时使用WiFi代理和VPN';
        } else if (isProxy) {
          _statusMessage = '检测到使用WiFi代理';
        } else if (isVpn) {
          _statusMessage = '检测到使用VPN';
        } else {
          _statusMessage = '未检测到代理或VPN';
        }
      });
    } catch (e) {
      setState(() {
        _statusMessage = '检测过程中出错: $e';
      });
    } finally {
      setState(() {
        _isChecking = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('网络状态检测')),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                '网络连接检测',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 20),
              Card(
                elevation: 4,
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      ListTile(
                        leading: Icon(
                          _isProxyUsed ? Icons.warning : Icons.check_circle,
                          color: _isProxyUsed ? Colors.red : Colors.green,
                        ),
                        title: const Text('WiFi代理状态'),
                        subtitle: Text(_isProxyUsed ? '正在使用代理' : '未使用代理'),
                      ),
                      const Divider(),
                      ListTile(
                        leading: Icon(
                          _isVpnUsed ? Icons.warning : Icons.check_circle,
                          color: _isVpnUsed ? Colors.red : Colors.green,
                        ),
                        title: const Text('VPN状态'),
                        subtitle: Text(_isVpnUsed ? '正在使用VPN' : '未使用VPN'),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 20),
              Text(
                _statusMessage,
                style: TextStyle(
                  fontSize: 16,
                  color: _isProxyUsed || _isVpnUsed ? Colors.red : Colors.black,
                ),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 30),
              ElevatedButton(
                onPressed: _isChecking ? null : _checkNetworkStatus,
                child:
                    _isChecking
                        ? const Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            CircularProgressIndicator(color: Colors.white),
                            SizedBox(width: 10),
                            Text('检测中...'),
                          ],
                        )
                        : const Text('检测网络状态'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
6.成品效果展示

三、结言

本文旨在记录,若有不当之处,还请指正。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值