Android2014.06.03 09:38




짬짬히 개발하고 있는 Smart Switch를 이미 이전에 개발을 한 분이 있군요. 그리고 지금 제가 느끼고 있는 문제도 해결해 놓았습니다. 다른 부연 설명은 하지 않고 정말로 좋은 아이디어 인것 같아 올립니다.

http://goltermann.cc/2011/11/android-accessing-wifi-even-in-standby-using-wakelock-wifilock-alarmmanager-and-services/

Android: Accessing Wifi even in Standby using WakeLock, WifiLock, Alarmmanager and Services

I wrote a little App to switch my phone in silent mode automatically as soon as a specific Wifi Network comes in range. I had some problems with using the wireless after the phone was gone into standby. Here is what I did and how to solve the problems I had with the standby mode.

My first attempt to do this was to register a BroadcastReceiver that listens on WifiManager.SCAN_RESULTS_AVAILABLE_ACTION Events. This event is fired as soon as the Android OS completes a Wlan scan, which is done approximately every ten to twenty seconds. The program than checks whether a certain SSID is in the results and silences the phone if it is the case. Here is the short version of the code:

public class WifiStateReciever extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		// Proceed only if received action is a wlan scan result
		if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) {

			// get List of networks in reach
			WifiManager connManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
			List<ScanResult> scanResults = connManager.getScanResults();

			// get list of silenced networks from preferences
			for(int i=0;i<scanResults.size();i++) {
				// if network is in in preferences and is checked then silence the phone
				if( ... certain network is this network ...) {
					mAudio.setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
					return;
				}
			}
		}
	}
}

Here is the corresponding manifest entry:

	
<receiver android:name="WifiStateReciever">
	<intent-filter>
		<action android:name="android.net.wifi.SCAN_RESULTS"></action>
	</intent-filter>
</receiver>
	

Problem:

The problem with this is that it stops working as soon as the phone goes into standby. My phone goes into standby 15 minutes after turning off the screen. CPU and Wifi is stopped during standby, only GSM is operating for incoming calls and a few other things. So my phone will not notice any new wireless network and hence not go into silent mode. So this way my app is missing the point.

Solution 1:

Fortunately there are ways to make this work. The simplest one is to prevent the phone from going into standby. (DO NOT DO THIS! Read on to see why and see the correct version below) This can be achieved using WakeLock and WifiLock. A WakeLock prevents the CPU, A WifiLock the Wlan from going into standby until they are released. Both are needed to get scan results from the phone all the time, even when it would be in standby. To hold these locks all the time you can use a service that is created as soon as the phone boots, acquire the locks in the onCreate() method and only release them in the onDestroy() method (the latter should only occur if the phone shuts down)

public class StartupReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		Intent serviceIntent = new Intent();
		serviceIntent.setAction("de.goltergaul.wlanSilence.WlanLockService");
		context.startService(serviceIntent);
	}

}

public class WlanLockService extends Service {

	private WifiLock wifiLock;
	private WakeLock wakeLock;

	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();

		WifiManager wm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
		wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL , "MyWifiLock");
		wifiLock.acquire();

		PowerManager powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
		wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "My Lock");
		wakeLock.acquire();
	}

	@Override
	public void onDestroy() {
		super.onDestroy();

		wifiLock.release();
		wakeLock.release();
	}
}

			
			
		

Here is the corresponding manifest entry:

<receiver android:name="StartupReceiver">
	<intent-filter>
		<action android:name="android.intent.action.BOOT_COMPLETED" />
		<category android:name="android.intent.category.HOME" />
	</intent-filter>
</receiver>
<service android:name="WlanLockService">
	<intent-filter>
		<action android:name="de.goltergaul.wlanSilence.WlanLockService" />
	</intent-filter>
</service>	
	

The downside of this approach is that it consumes a huge amount of extra energy because you eliminate the standby mode completely.screenshot-1320755332329 The figure on the right is showing the energy consumption of all processes. WlanSilncer is the application preventing the standby mode. Therefore this is not the way to do it!

Solution 2:

The second option is to use the Android AlarmManager. That is a service that can wake up the phone after a certain period of time, execute an PendingIntent (A block of code) and then go to sleep again. This way the the phone can go into standby and just wakes up to scan for new wireless networks and goes to sleep again right after that. This way is much more energy efficient. To implement this I wrote another BroadcastReceiver which receives events from the AlarmManager:

	
public class WlanScanner extends BroadcastReceiver {

	private AlarmManager alarmMgr;
	private PendingIntent pendingIntent;

	private static WifiLock wifiLock;
	private static WakeLock wakeLock;

	public static void lock() {
		try {
			wakeLock.acquire();
			wifiLock.acquire();
		} catch(Exception e) {
			Log.e("WlanSilencer", "Error getting Lock: "+e.getMessage());
		}
	}

	public static void unlock() {
		if(wakeLock.isHeld())
			wakeLock.release();
		if(wifiLock.isHeld())
			wifiLock.release();
	}

	public WlanScanner() {} // called by the AlarmManager

	// this constructor is invoked by WlanLockService (see next code snippet)
	public WlanScanner(Context context, int timeoutInSeconds){

		// initialise the locks
		wifiLock = ((WifiManager) context.getSystemService(Context.WIFI_SERVICE))
						.createWifiLock(WifiManager.WIFI_MODE_SCAN_ONLY , "WlanSilencerScanLock");
		wakeLock = ((PowerManager) context.getSystemService(Context.POWER_SERVICE))
						.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WlanSilencerWakeLock");

        	alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

		// use this class as the receiver
        	Intent intent = new Intent(context, WlanScanner.class);
        	// create a PendingIntent that can be passed to the AlarmManager
        	pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        	// create a repeating alarm, that goes of every x seconds
        	// AlarmManager.ELAPSED_REALTIME_WAKEUP = wakes up the cpu only
       		alarmMgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), timeoutInSeconds*1000, pendingIntent);
	}

	// stop the repeating alarm
	public void stop() {
		alarmMgr.cancel(pendingIntent);
	}

	@Override
	public void onReceive(Context context, Intent arg1) {
		Log.v("WlanSilencer", "Alarm received");
		WifiManager connManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
		if(connManager.isWifiEnabled()) {
			lock(); // lock wakeLock and wifiLock, then scan.
			// unlock() is then called at the end of the onReceive function of WifiStateReciever
			connManager.startScan();
		}
	}

}

	
	
public class WlanLockService extends Service {

	private WlanScanner scanner;

	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		scanner = new WlanScanner(this, 60);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();

		scanner.stop();
		scanner = null;
	}
}

	

Summary:

The code has to do this in the right order:

  1. After the device booted the WlanLockService has to be started
  2. The WlanLockService has to initialize WlanScanner which sets a repeated Alarm using the AlarmManager
  3. Once the alarm is received a WakeLock and a WifiLock should be acquired
  4. A wlan scan can now be requested
  5. The wlan scan result event is received and processed
  6. Release the WakeLock and WifiLock
신고




Posted by 금붕어70