From fd01712b6c587d62763ce40166fd7c64af1783de Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 26 Dec 2015 17:14:47 +0800 Subject: [PATCH 01/10] Reduce battery usage - use notify instead of startForeground --- .../github/shadowsocks/ShadowsocksNotification.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala index f8f8b06e..4c609f53 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala @@ -2,7 +2,7 @@ package com.github.shadowsocks import java.util.Locale -import android.app.{KeyguardManager, PendingIntent} +import android.app.{KeyguardManager, NotificationManager, PendingIntent} import android.content.{BroadcastReceiver, Context, Intent, IntentFilter} import android.os.PowerManager import android.support.v4.app.NotificationCompat @@ -16,6 +16,7 @@ import com.github.shadowsocks.utils.{Action, State, Utils} */ class ShadowsocksNotification(private val service: BaseService, profileName: String, visible: Boolean = false) { private lazy val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager] + private lazy val nm = service.getSystemService(Context.NOTIFICATION_SERVICE).asInstanceOf[NotificationManager] private lazy val callback = new Stub { override def stateChanged(state: Int, msg: String) = () // ignore override def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) { @@ -27,6 +28,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str } private var lockReceiver: BroadcastReceiver = _ private var callbackRegistered: Boolean = _ + private var started: Boolean = _ private val builder = new NotificationCompat.Builder(service) .setWhen(0) @@ -73,8 +75,10 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str show() } else if (forceShow) show() - def notification = builder.build() - def show() = service.startForeground(1, notification) + def show() = if (started) nm.notify(1, builder.build) else { + service.startForeground(1, builder.build) + started = true + } def destroy() { if (lockReceiver != null) { From 971ea09077257868f961d02136a9d073a3fad2e8 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 26 Dec 2015 17:25:18 +0800 Subject: [PATCH 02/10] Reduce battery usage - optimize performance critical block --- .../com/github/shadowsocks/BaseService.scala | 32 ++++++------- .../shadowsocks/utils/TrafficMonitor.scala | 48 ++++++------------- 2 files changed, 30 insertions(+), 50 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala index 0c81975a..aaaae236 100644 --- a/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -87,8 +87,7 @@ trait BaseService extends Service { val task = new TimerTask { def run { TrafficMonitor.updateRate() - updateTrafficRate(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate, - TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal) + updateTrafficRate() } } timer = new Timer(true) @@ -122,7 +121,7 @@ trait BaseService extends Service { def stopRunner() { // Make sure update total traffic when stopping the runner - updateTrafficTotal(TrafficMonitor.getDeltaTx, TrafficMonitor.getDeltaRx) + updateTrafficTotal(TrafficMonitor.txTotal, TrafficMonitor.rxTotal) TrafficMonitor.reset() if (trafficMonitorThread != null) { @@ -132,19 +131,16 @@ trait BaseService extends Service { } def updateTrafficTotal(tx: Long, rx: Long) { - val handler = new Handler(getContext.getMainLooper) - handler.post(() => { - val config = this.config // avoid race conditions without locking - if (config != null) { - ShadowsocksApplication.profileManager.getProfile(config.profileId) match { - case Some(profile) => - profile.tx += tx - profile.rx += rx - ShadowsocksApplication.profileManager.updateProfile(profile) - case None => // Ignore - } + val config = this.config // avoid race conditions without locking + if (config != null) { + ShadowsocksApplication.profileManager.getProfile(config.profileId) match { + case Some(profile) => + profile.tx += tx + profile.rx += rx + ShadowsocksApplication.profileManager.updateProfile(profile) + case None => // Ignore } - }) + } } def stopBackgroundService() @@ -162,10 +158,14 @@ trait BaseService extends Service { changeState(s, null) } - def updateTrafficRate(txRate: String, rxRate: String, txTotal: String, rxTotal: String) { + def updateTrafficRate() { val handler = new Handler(getContext.getMainLooper) handler.post(() => { if (callbackCount > 0) { + val txRate = TrafficMonitor.getTxRate + val rxRate = TrafficMonitor.getRxRate + val txTotal = TrafficMonitor.getTxTotal + val rxTotal = TrafficMonitor.getRxTotal val n = callbacks.beginBroadcast() for (i <- 0 until n) { try { diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala index d34b526f..5ae6d7fa 100644 --- a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala +++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala @@ -5,26 +5,19 @@ import java.text.DecimalFormat import com.github.shadowsocks.{R, ShadowsocksApplication} -case class Traffic(tx: Long, rx: Long, timestamp: Long) - object TrafficMonitor { - var last: Traffic = getTraffic(0, 0) - // Bytes per second - var txRate: Long = 0 - var rxRate: Long = 0 + var txRate: Long = _ + var rxRate: Long = _ // Bytes for the current session - var txTotal: Long = 0 - var rxTotal: Long = 0 + var txTotal: Long = _ + var rxTotal: Long = _ // Bytes for the last query - var txLast: Long = 0 - var rxLast: Long = 0 - - def getTraffic(tx: Long, rx: Long): Traffic = { - new Traffic(tx, rx, System.currentTimeMillis()) - } + var txLast: Long = _ + var rxLast: Long = _ + var timestampLast: Long = _ private val units = Array("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB") private val numberFormat = new DecimalFormat("@@@") @@ -40,15 +33,15 @@ object TrafficMonitor { } def updateRate() { - val now = getTraffic(txTotal, rxTotal) - val delta = now.timestamp - last.timestamp - val deltaTx = now.tx - last.tx - val deltaRx = now.rx - last.rx + val now = System.currentTimeMillis() + val delta = now - timestampLast if (delta != 0) { - txRate = deltaTx * 1000 / delta - rxRate = deltaRx * 1000 / delta + txRate = (txTotal - txLast) * 1000 / delta + rxRate = (rxTotal - rxLast) * 1000 / delta + txLast = txTotal + rxLast = rxTotal + timestampLast = now } - last = now } def update(tx: Long, rx: Long) { @@ -63,7 +56,6 @@ object TrafficMonitor { rxTotal = 0 txLast = 0 rxLast = 0 - last = getTraffic(0, 0) } def getTxTotal(): String = { @@ -89,17 +81,5 @@ object TrafficMonitor { def getRate(): String = { formatTraffic(txRate + rxRate) } - - def getDeltaTx(): Long = { - val last = txLast - txLast = txTotal - txTotal - last - } - - def getDeltaRx(): Long = { - val last = rxLast - rxLast = rxTotal - rxTotal - last - } } From 569f056a22adb0236f4eb1c44dc4fb8fff01bb77 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 26 Dec 2015 18:56:12 +0800 Subject: [PATCH 03/10] Reduce battery usage - register broadcast at runtime --- src/main/AndroidManifest.xml | 8 ------ .../com/github/shadowsocks/AppManager.scala | 26 ++++++++++++++----- .../shadowsocks/AppManagerReceiver.scala | 11 -------- 3 files changed, 19 insertions(+), 26 deletions(-) delete mode 100644 src/main/scala/com/github/shadowsocks/AppManagerReceiver.scala diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index f17450ae..ad614eae 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -108,14 +108,6 @@ - - - - - - - - diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala index 7e86eb9f..fb1eab80 100644 --- a/src/main/scala/com/github/shadowsocks/AppManager.scala +++ b/src/main/scala/com/github/shadowsocks/AppManager.scala @@ -40,7 +40,7 @@ package com.github.shadowsocks import android.content.pm.PackageManager -import android.content.{ClipData, ClipboardManager, Context} +import android.content._ import android.graphics.PixelFormat import android.graphics.drawable.Drawable import android.os.{Bundle, Handler} @@ -65,13 +65,25 @@ object AppManager { case class ProxiedApp(name: String, packageName: String, icon: Drawable) private case class ListEntry(switch: Switch, text: TextView, icon: ImageView) - var cachedApps: Array[ProxiedApp] = _ + private var receiverRegistered: Boolean = _ + private var cachedApps: Array[ProxiedApp] = _ private def getApps(pm: PackageManager) = { - if (cachedApps == null) cachedApps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS) - .filter(p => p.requestedPermissions != null && p.requestedPermissions.contains(permission.INTERNET)) - .map(p => new ProxiedApp(pm.getApplicationLabel(p.applicationInfo).toString, p.packageName, - p.applicationInfo.loadIcon(pm))).toArray - cachedApps + if (!receiverRegistered) { + val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED) + filter.addAction(Intent.ACTION_PACKAGE_REMOVED) + filter.addDataScheme("package") + ShadowsocksApplication.instance.registerReceiver((context: Context, intent: Intent) => + if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED || !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) + synchronized(cachedApps = null), filter) + receiverRegistered = true + } + synchronized { + if (cachedApps == null) cachedApps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS) + .filter(p => p.requestedPermissions != null && p.requestedPermissions.contains(permission.INTERNET)) + .map(p => new ProxiedApp(pm.getApplicationLabel(p.applicationInfo).toString, p.packageName, + p.applicationInfo.loadIcon(pm))).toArray + cachedApps + } } } diff --git a/src/main/scala/com/github/shadowsocks/AppManagerReceiver.scala b/src/main/scala/com/github/shadowsocks/AppManagerReceiver.scala deleted file mode 100644 index f3170f21..00000000 --- a/src/main/scala/com/github/shadowsocks/AppManagerReceiver.scala +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.shadowsocks - -import android.content.{Intent, Context, BroadcastReceiver} - -/** - * @author Mygod - */ -class AppManagerReceiver extends BroadcastReceiver { - override def onReceive(context: Context, intent: Intent) = if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED || - !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) AppManager.synchronized(AppManager.cachedApps = null) -} From ef162cd47e85411504b468351c1282cf7621e545 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 26 Dec 2015 19:21:03 +0800 Subject: [PATCH 04/10] Reload apps if a change is detected --- .../com/github/shadowsocks/AppManager.scala | 99 ++++++++++--------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala index fb1eab80..9f0d9bba 100644 --- a/src/main/scala/com/github/shadowsocks/AppManager.scala +++ b/src/main/scala/com/github/shadowsocks/AppManager.scala @@ -39,8 +39,11 @@ package com.github.shadowsocks -import android.content.pm.PackageManager +import java.util.concurrent.atomic.AtomicBoolean + +import android.Manifest.permission import android.content._ +import android.content.pm.PackageManager import android.graphics.PixelFormat import android.graphics.drawable.Drawable import android.os.{Bundle, Handler} @@ -54,7 +57,6 @@ import android.view._ import android.widget.AbsListView.OnScrollListener import android.widget.CompoundButton.OnCheckedChangeListener import android.widget._ -import android.Manifest.permission import com.github.shadowsocks.utils.{Key, Utils} import scala.collection.JavaConversions._ @@ -65,6 +67,8 @@ object AppManager { case class ProxiedApp(name: String, packageName: String, icon: Drawable) private case class ListEntry(switch: Switch, text: TextView, icon: ImageView) + private var instance: AppManager = _ + private var receiverRegistered: Boolean = _ private var cachedApps: Array[ProxiedApp] = _ private def getApps(pm: PackageManager) = { @@ -73,8 +77,12 @@ object AppManager { filter.addAction(Intent.ACTION_PACKAGE_REMOVED) filter.addDataScheme("package") ShadowsocksApplication.instance.registerReceiver((context: Context, intent: Intent) => - if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED || !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) - synchronized(cachedApps = null), filter) + if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED || + !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + synchronized(cachedApps = null) + val instance = AppManager.instance + if (instance != null) instance.reloadApps() + }, filter) receiverRegistered = true } synchronized { @@ -98,43 +106,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC private var loadingView: View = _ private var overlay: TextView = _ private var adapter: ListAdapter = _ - @volatile private var appsLoading: Boolean = _ - - def loadApps() { - appsLoading = true - proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet] - apps = getApps(getPackageManager).sortWith((a, b) => { - val aProxied = proxiedApps.contains(a.packageName) - if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0 - }) - adapter = new ArrayAdapter[ProxiedApp](this, R.layout.layout_apps_item, R.id.itemtext, apps) { - override def getView(position: Int, view: View, parent: ViewGroup): View = { - var convertView = view - var entry: ListEntry = null - if (convertView == null) { - convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false) - entry = new ListEntry(convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch], - convertView.findViewById(R.id.itemtext).asInstanceOf[TextView], - convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView]) - convertView.setOnClickListener(AppManager.this) - convertView.setTag(entry) - entry.switch.setOnCheckedChangeListener(AppManager.this) - } else { - entry = convertView.getTag.asInstanceOf[ListEntry] - } - - val app: ProxiedApp = apps(position) - - entry.text.setText(app.name) - entry.icon.setImageDrawable(app.icon) - val switch = entry.switch - switch.setTag(app) - switch.setChecked(proxiedApps.contains(app.packageName)) - entry.text.setTag(switch) - convertView - } - } - } + private val appsLoading = new AtomicBoolean private def setProxied(pn: String, proxied: Boolean) = if (proxied) proxiedApps.add(pn) else proxiedApps.remove(pn) @@ -157,6 +129,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC } override def onDestroy() { + instance = null super.onDestroy() if (handler != null) { handler.removeCallbacksAndMessages(null) @@ -198,7 +171,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC // Restart activity appListView.setVisibility(View.GONE) loadingView.setVisibility(View.VISIBLE) - if (appsLoading) appsLoading = false else loadAppsAsync() + reloadApps() return true } } @@ -264,13 +237,49 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC } } }) + instance = this loadAppsAsync() } + def reloadApps() = if (!appsLoading.compareAndSet(true, false)) loadAppsAsync() def loadAppsAsync() { + if (!appsLoading.compareAndSet(false, true)) return ThrowableFuture { - while (!appsLoading) loadApps() - appsLoading = false + do { + proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet] + appsLoading.set(true) + apps = getApps(getPackageManager).sortWith((a, b) => { + val aProxied = proxiedApps.contains(a.packageName) + if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0 + }) + adapter = new ArrayAdapter[ProxiedApp](this, R.layout.layout_apps_item, R.id.itemtext, apps) { + override def getView(position: Int, view: View, parent: ViewGroup): View = { + var convertView = view + var entry: ListEntry = null + if (convertView == null) { + convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false) + entry = new ListEntry(convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch], + convertView.findViewById(R.id.itemtext).asInstanceOf[TextView], + convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView]) + convertView.setOnClickListener(AppManager.this) + convertView.setTag(entry) + entry.switch.setOnCheckedChangeListener(AppManager.this) + } else { + entry = convertView.getTag.asInstanceOf[ListEntry] + } + + val app: ProxiedApp = apps(position) + + entry.text.setText(app.name) + entry.icon.setImageDrawable(app.icon) + val switch = entry.switch + switch.setTag(app) + switch.setChecked(proxiedApps.contains(app.packageName)) + entry.text.setTag(switch) + convertView + } + } + } while (!appsLoading.compareAndSet(true, false)) handler.post(() => { appListView.setAdapter(adapter) Utils.crossFade(AppManager.this, loadingView, appListView) @@ -279,7 +288,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC } def saveAppSettings(context: Context) { - if (!appsLoading) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply + if (!appsLoading.get) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply } var handler: Handler = null From 8dcfd5a8c5afca445ea21eaaea962c01e5c63eb5 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 26 Dec 2015 19:59:58 +0800 Subject: [PATCH 05/10] Reduce battery usage - prevent unnecessary notification updates --- .../com/github/shadowsocks/BaseService.scala | 6 ++- .../shadowsocks/utils/TrafficMonitor.scala | 37 +++++++++++++++---- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala index aaaae236..777290a5 100644 --- a/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -86,8 +86,7 @@ trait BaseService extends Service { if (callbackCount == 0 && timer == null) { val task = new TimerTask { def run { - TrafficMonitor.updateRate() - updateTrafficRate() + if (TrafficMonitor.updateRate()) updateTrafficRate() } } timer = new Timer(true) @@ -95,6 +94,9 @@ trait BaseService extends Service { } callbacks.register(cb) callbackCount += 1 + TrafficMonitor.updateRate() + cb.trafficUpdated(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate, + TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal) } } diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala index 5ae6d7fa..168fd6ed 100644 --- a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala +++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala @@ -18,6 +18,7 @@ object TrafficMonitor { var txLast: Long = _ var rxLast: Long = _ var timestampLast: Long = _ + @volatile var dirty = true private val units = Array("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB") private val numberFormat = new DecimalFormat("@@@") @@ -32,21 +33,42 @@ object TrafficMonitor { else numberFormat.format(n) + ' ' + units(i) } - def updateRate() { + def updateRate() = { val now = System.currentTimeMillis() val delta = now - timestampLast + var updated = false if (delta != 0) { - txRate = (txTotal - txLast) * 1000 / delta - rxRate = (rxTotal - rxLast) * 1000 / delta - txLast = txTotal - rxLast = rxTotal + if (dirty) { + txRate = (txTotal - txLast) * 1000 / delta + rxRate = (rxTotal - rxLast) * 1000 / delta + txLast = txTotal + rxLast = rxTotal + dirty = false + updated = true + } else { + if (txRate != 0) { + txRate = 0 + updated = true + } + if (rxRate != 0) { + rxRate = 0 + updated = true + } + } timestampLast = now } + updated } def update(tx: Long, rx: Long) { - txTotal = tx - rxTotal = rx + if (txTotal != tx) { + txTotal = tx + dirty = true + } + if (rxTotal != rx) { + rxTotal = rx + dirty = true + } } def reset() { @@ -56,6 +78,7 @@ object TrafficMonitor { rxTotal = 0 txLast = 0 rxLast = 0 + dirty = true } def getTxTotal(): String = { From 6bff4f1a441ee83dca7f71da5f062c80fcc24681 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 26 Dec 2015 20:41:23 +0800 Subject: [PATCH 06/10] Reduce battery usage - transmit traffic stat with binary --- src/main/jni/shadowsocks-libev | 2 +- .../shadowsocks/utils/TrafficMonitorThread.scala | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/main/jni/shadowsocks-libev b/src/main/jni/shadowsocks-libev index 48a063ef..68eaf3b1 160000 --- a/src/main/jni/shadowsocks-libev +++ b/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 48a063ef1e1a886e274a0fbb797e88358deca35b +Subproject commit 68eaf3b12d385381130c046547163b81e1807ba4 diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala index 15e9a43c..ec4b890e 100644 --- a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala +++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala @@ -40,11 +40,11 @@ package com.github.shadowsocks.utils import java.io.{File, IOException} +import java.nio.{ByteBuffer, ByteOrder} import java.util.concurrent.Executors import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress} import android.util.Log -import com.github.shadowsocks.BaseService class TrafficMonitorThread() extends Thread { @@ -99,16 +99,10 @@ class TrafficMonitorThread() extends Thread { val input = socket.getInputStream val output = socket.getOutputStream - val buffer = new Array[Byte](256) - val size = input.read(buffer) - - val array = new Array[Byte](size) - Array.copy(buffer, 0, array, 0, size) - - val stat = new String(array, "UTF-8").split("\\|") - if (stat.length == 2) { - TrafficMonitor.update(stat(0).toLong, stat(1).toLong) - } + val buffer = new Array[Byte](16) + if (input.read(buffer) != 16) throw new IOException("Unexpected traffic stat length") + val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN) + TrafficMonitor.update(stat.getLong(0), stat.getLong(8)) output.write(0) From 179aab80942fb258ded3714d14d1fa1716fddce2 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 26 Dec 2015 22:32:23 +0800 Subject: [PATCH 07/10] Reduce battery usage - Reduce expensive reflections --- .../com/github/shadowsocks/ShadowsocksVpnThread.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala index 6946f8cd..f3fbb3e2 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala @@ -45,7 +45,12 @@ import java.util.concurrent.Executors import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress} import android.util.Log +object ShadowsocksVpnThread { + val getInt = classOf[FileDescriptor].getDeclaredMethod("getInt$") +} + class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread { + import ShadowsocksVpnThread._ val TAG = "ShadowsocksVpnService" val PATH = "/data/data/com.github.shadowsocks/protect_path" @@ -103,11 +108,8 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread { val fds = socket.getAncillaryFileDescriptors if (fds.nonEmpty) { - var ret = false - - val getInt = classOf[FileDescriptor].getDeclaredMethod("getInt$") val fd = getInt.invoke(fds(0)).asInstanceOf[Int] - ret = vpnService.protect(fd) + val ret = vpnService.protect(fd) // Trick to close file decriptor System.jniclose(fd) From 51ceb3dac55992a59d08aae7df193d515df2ded2 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 26 Dec 2015 23:10:54 +0800 Subject: [PATCH 08/10] Refine callback handling --- .../com/github/shadowsocks/BaseService.scala | 20 +++++-------------- .../shadowsocks/ShadowsocksNatService.scala | 10 ++-------- .../shadowsocks/ShadowsocksVpnService.scala | 10 ++-------- 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala index 777290a5..a2b7528b 100644 --- a/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -50,7 +50,6 @@ import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread trait BaseService extends Service { @volatile private var state = State.STOPPED - @volatile private var callbackCount = 0 @volatile protected var config: Config = null var timer: Timer = null @@ -70,20 +69,17 @@ trait BaseService extends Service { override def unregisterCallback(cb: IShadowsocksServiceCallback) { if (cb != null ) { callbacks.unregister(cb) - callbackCount -= 1 } - if (callbackCount == 0 && timer != null) { + if (callbacks.getRegisteredCallbackCount == 0 && timer != null) { timer.cancel() timer = null } - if (callbackCount == 0 && state == State.STOPPED) { - stopBackgroundService() - } } override def registerCallback(cb: IShadowsocksServiceCallback) { if (cb != null) { - if (callbackCount == 0 && timer == null) { + callbacks.register(cb) + if (callbacks.getRegisteredCallbackCount != 0 && timer == null) { val task = new TimerTask { def run { if (TrafficMonitor.updateRate()) updateTrafficRate() @@ -92,8 +88,6 @@ trait BaseService extends Service { timer = new Timer(true) timer.schedule(task, 1000, 1000) } - callbacks.register(cb) - callbackCount += 1 TrafficMonitor.updateRate() cb.trafficUpdated(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate, TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal) @@ -145,14 +139,10 @@ trait BaseService extends Service { } } - def stopBackgroundService() def getServiceMode: Int def getTag: String def getContext: Context - def getCallbackCount: Int = { - callbackCount - } def getState: Int = { state } @@ -163,7 +153,7 @@ trait BaseService extends Service { def updateTrafficRate() { val handler = new Handler(getContext.getMainLooper) handler.post(() => { - if (callbackCount > 0) { + if (callbacks.getRegisteredCallbackCount > 0) { val txRate = TrafficMonitor.getTxRate val rxRate = TrafficMonitor.getRxRate val txTotal = TrafficMonitor.getTxTotal @@ -184,7 +174,7 @@ trait BaseService extends Service { protected def changeState(s: Int, msg: String) { val handler = new Handler(getContext.getMainLooper) handler.post(() => if (state != s) { - if (callbackCount > 0) { + if (callbacks.getRegisteredCallbackCount > 0) { val n = callbacks.beginBroadcast() for (i <- 0 until n) { try { diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index f25becb5..93cf2327 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -476,19 +476,13 @@ class ShadowsocksNatService extends BaseService { // reset NAT killProcesses() - // stop the service if no callback registered - if (getCallbackCount == 0) { - stopSelf() - } + // stop the service if nothing has bound to it + stopSelf() // change the state changeState(State.STOPPED) } - override def stopBackgroundService() { - stopSelf() - } - override def getTag = TAG override def getServiceMode = Mode.NAT override def getContext = getBaseContext diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 849af214..9e3c5f31 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -136,10 +136,8 @@ class ShadowsocksVpnService extends VpnService with BaseService { conn = null } - // stop the service if no callback registered - if (getCallbackCount == 0) { - stopSelf() - } + // stop the service if nothing has bound to it + stopSelf() // clean up recevier if (closeReceiver != null) { @@ -458,10 +456,6 @@ class ShadowsocksVpnService extends VpnService with BaseService { fd } - override def stopBackgroundService() { - stopSelf() - } - override def getTag = TAG override def getServiceMode = Mode.VPN From 0018a7ca9e204ac27d623eeb490284fd96b613ea Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 26 Dec 2015 23:26:48 +0800 Subject: [PATCH 09/10] Fix NAT mode not working after task removed from recent --- src/main/scala/com/github/shadowsocks/BaseService.scala | 9 ++++++++- .../com/github/shadowsocks/ServiceBoundContext.scala | 2 -- .../com/github/shadowsocks/ShadowsocksNatService.scala | 8 +------- .../com/github/shadowsocks/ShadowsocksVpnService.scala | 8 +------- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala index a2b7528b..67ea792d 100644 --- a/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -42,7 +42,7 @@ package com.github.shadowsocks import java.util.{Timer, TimerTask} import android.app.Service -import android.content.Context +import android.content.{Intent, Context} import android.os.{Handler, RemoteCallbackList} import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread} @@ -110,6 +110,7 @@ trait BaseService extends Service { def startRunner(config: Config) { this.config = config + startService(new Intent(getContext, getClass)) TrafficMonitor.reset() trafficMonitorThread = new TrafficMonitorThread() trafficMonitorThread.start() @@ -124,6 +125,12 @@ trait BaseService extends Service { trafficMonitorThread.stopThread() trafficMonitorThread = null } + + // change the state + changeState(State.STOPPED) + + // stop the service if nothing has bound to it + stopSelf() } def updateTrafficTotal(tx: Long, rx: Long) { diff --git a/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala b/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala index c304f559..8a68a761 100644 --- a/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala +++ b/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala @@ -54,8 +54,6 @@ trait ServiceBoundContext extends Context { connection = new ShadowsocksServiceConnection() bindService(intent, connection, Context.BIND_AUTO_CREATE) - - startService(new Intent(this, s)) } } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 93cf2327..45f93c87 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -458,8 +458,6 @@ class ShadowsocksNatService extends BaseService { override def stopRunner() { - super.stopRunner() - // channge the state changeState(State.STOPPING) @@ -476,11 +474,7 @@ class ShadowsocksNatService extends BaseService { // reset NAT killProcesses() - // stop the service if nothing has bound to it - stopSelf() - - // change the state - changeState(State.STOPPED) + super.stopRunner() } override def getTag = TAG diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 9e3c5f31..d0ecfeaf 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -113,8 +113,6 @@ class ShadowsocksVpnService extends VpnService with BaseService { override def stopRunner() { - super.stopRunner() - if (vpnThread != null) { vpnThread.stopThread() vpnThread = null @@ -136,17 +134,13 @@ class ShadowsocksVpnService extends VpnService with BaseService { conn = null } - // stop the service if nothing has bound to it - stopSelf() - // clean up recevier if (closeReceiver != null) { unregisterReceiver(closeReceiver) closeReceiver = null } - // channge the state - changeState(State.STOPPED) + super.stopRunner() } def getVersionName: String = { From 4d7bf8d7071ecd2639df8ab6edeb7f9a1c476dd7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 26 Dec 2015 23:46:18 +0800 Subject: [PATCH 10/10] Reduce battery usage - Unregister callbacks onStop --- .../shadowsocks/ProfileManagerActivity.scala | 9 ++++++ .../shadowsocks/ServiceBoundContext.scala | 32 ++++++++++--------- .../com/github/shadowsocks/Shadowsocks.scala | 5 +++ 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala index 3eef2882..401cbd6d 100644 --- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala @@ -218,6 +218,15 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe } } + override def onStart() { + super.onStart() + registerCallback + } + override def onStop() { + super.onStop() + unregisterCallback + } + override def onDestroy { deattachService() super.onDestroy diff --git a/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala b/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala index 8a68a761..f50cd606 100644 --- a/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala +++ b/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala @@ -13,20 +13,12 @@ trait ServiceBoundContext extends Context { class ShadowsocksServiceConnection extends ServiceConnection { override def onServiceConnected(name: ComponentName, service: IBinder) { bgService = IShadowsocksService.Stub.asInterface(service) - if (callback != null) try { - bgService.registerCallback(callback) - } catch { - case ignored: RemoteException => // Nothing - } + registerCallback ServiceBoundContext.this.onServiceConnected() } override def onServiceDisconnected(name: ComponentName) { if (callback != null) { - try { - if (bgService != null) bgService.unregisterCallback(callback) - } catch { - case ignored: RemoteException => // Nothing - } + unregisterCallback callback = null } ServiceBoundContext.this.onServiceDisconnected() @@ -34,11 +26,25 @@ trait ServiceBoundContext extends Context { } } + def registerCallback = if (bgService != null && callback != null && !callbackRegistered) try { + bgService.registerCallback(callback) + callbackRegistered = true + } catch { + case ignored: RemoteException => // Nothing + } + def unregisterCallback = if (bgService != null && callback != null && callbackRegistered) try { + bgService.unregisterCallback(callback) + callbackRegistered = false + } catch { + case ignored: RemoteException => // Nothing + } + def onServiceConnected() = () def onServiceDisconnected() = () private var callback: IShadowsocksServiceCallback.Stub = _ private var connection: ShadowsocksServiceConnection = _ + private var callbackRegistered: Boolean = _ // Variables var bgService: IShadowsocksService = _ @@ -60,11 +66,7 @@ trait ServiceBoundContext extends Context { def deattachService() { if (bgService != null) { if (callback != null) { - try { - bgService.unregisterCallback(callback) - } catch { - case ignored: RemoteException => // Nothing - } + unregisterCallback callback = null } if (connection != null) { diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 2c739d72..2f949e41 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -461,8 +461,13 @@ class Shadowsocks preferences.update(profile) } + override def onStart() { + super.onStart() + registerCallback + } override def onStop() { super.onStop() + unregisterCallback clearDialog() }