我今天有个业务需求就是对商用设备进行流量限制,防止流量无故消耗,因此,要对APP的使用网络进行限制,并不需要root,嗯,VpnService就是解决这件事情的。先把代码贴出来:
private static final String TAG = "NetGuard.Service";
private static final String EXTRA_COMMAND = "Command";
private ParcelFileDescriptor vpn = null;
public static final int START = 1;
public static final int RELOAD = 2;
public static final int STOP = 3;
@Override
public void onCreate() {
// Listen for connectivity updates
IntentFilter ifConnectivity = new IntentFilter();
ifConnectivity.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(connectivityChangedReceiver, ifConnectivity);
super.onCreate();
}
private BroadcastReceiver connectivityChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Received " + intent);
if (intent.hasExtra(ConnectivityManager.EXTRA_NETWORK_TYPE))
reload(BlackHoleService.this);
}
};
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Get command
int cmd = intent.getIntExtra(EXTRA_COMMAND, RELOAD);
Log.e(TAG, "执行:" + cmd);
// Process command
switch (cmd) {
case START:
if (NetworkUtils.isNetworkAvailable(this) && vpn == null) {
vpnStart();
}
break;
case RELOAD:
ParcelFileDescriptor prev = vpn;
vpnStart();
if (prev != null)
vpnStop(prev);
break;
case STOP:
if (vpn != null) {
vpnStop(vpn);
vpn = null;
}
stopSelf();
break;
}
return START_STICKY;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void vpnStart() {
Log.e(TAG, "Starting");
final Builder builder = new Builder();
builder.setSession(getString(R.string.app_name));
builder.addAddress("10.1.10.1", 32);
builder.addAddress("fd00:1:fd00:1:fd00:1:fd00:1", 128);
builder.addRoute("0.0.0.0", 0);
builder.addRoute("0:0:0:0:0:0:0:0", 0);
try {
builder.addDisallowedApplication(MainActivity.ALLOW_PACKAGE_NAME);
builder.addDisallowedApplication("com.google.android.gms");
builder.addDisallowedApplication(getPackageName());
vpn = builder.establish();
Log.e(TAG, "启动完成");
} catch (Exception e) {
Log.e(TAG, "大爷的,是不是这里有问题?");
Log.e(TAG, e.toString());
}
}
private void vpnStop(ParcelFileDescriptor prev) {
if (prev != null) {
try {
prev.close();
} catch (IOException ex) {
Log.e(TAG, ex.toString() + "\n" + Log.getStackTraceString(ex));
}
}
}
public static void start(Context context) {
Intent intent = new Intent(context, BlackHoleService.class);
intent.putExtra(EXTRA_COMMAND, START);
context.startService(intent);
}
public static void stop(Context context) {
Intent intent = new Intent(context, BlackHoleService.class);
intent.putExtra(EXTRA_COMMAND, STOP);
context.startService(intent);
}
public static void reload(Context context) {
if (BlockUtils.isLock()) {
Intent intent = new Intent(context, BlackHoleService.class);
intent.putExtra(EXTRA_COMMAND, RELOAD);
context.startService(intent);
}
}
public static Intent isVpnServicePrepared(Context context) {
Intent prepare = null;
try {
return VpnService.prepare(context.getApplicationContext());
} catch (Exception ex) {
Log.e(TAG, ex.toString());
}
return prepare;
}
@Override
public void onDestroy() {
Log.e(TAG, "VPNService Destroy");
unregisterReceiver(connectivityChangedReceiver);
super.onDestroy();
}
在AndroidManifest.xml中注册:
<service
"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action />
</intent-filter>
<meta-data
android:value="true" />
</service>
这里要注意的是,权限android:permission="android.permission.BIND_VPN_SERVICE"必须在<service>下面,写在外面是没用的。
到这里就基本实现了,我这里用的IP地址都是无效的,所以,addDisallowedApplication真正的含义在于排除,也就是addDisallowedApplication加进去的都是可以正常使用网络的。
经过测试,问题来,就是当手机重启之后,发现服务一直被杀掉。别跟我说什么保活,在这里是没用的。因为根本原因不在于service的生命周期上。经过反复测试,发现一个现象就是手机开机的时候,加载网络驱动是需要时间的,网络连接也是需要时间的。如果在网络没有连接的时候,去调用vpnStart() ,它就会被系统杀死。没办法,只好先做一个网络连接判断,如果没有网络就不去调用,当监听到网络正常时,再去启动它。到此,问题就被修复了。