一个android service下多任务的设计
来源:互联网 发布:淘宝上大连甜品店 编辑:程序博客网 时间:2024/05/16 01:13
在android开发当中我们时常会使用到service,往往我们会直接开启一个服务在服务中开启各种线程做各种事件,这样当然可以完成一个编码的任务但耦合性非常高,一旦有需求改动就要大批量改代码,偶然的机会看到一个大师对这块的处理感觉不错,写出来记录下供参考学习,下次碰到相关服务可以采用这样的方式设计减低耦合性。
//定义一个通用接口
public interface ProxyableService {
public void onCreate(ServiceProxy c);
public void onDestroy();
public int onStartCommand(Intent intent, int flags, int startId);
}
//实现一个抽象服务基类继承服务
public abstract class ServiceBindable extends Service {
protected boolean started;
protected ServiceBinder binder;
@Override
public void onCreate() {
super.onCreate();
this.binder = new ServiceBinder(this);
}
abstract protected void onStartOnce();
@Override
public IBinder onBind(Intent intent) {
if (!this.started) {
this.started = true;
onStartOnce();
}
return this.binder;
}
public class ServiceBinder extends Binder {
private WeakReference<ServiceBindable> mService;
public ServiceBinder(ServiceBindable serviceBindable) {
this.mService = new WeakReference<ServiceBindable>(serviceBindable);
}
public ServiceBindable getService() {
return this.mService.get();
}
public void close() {
this.mService = null;
}
}
@Override
public void onDestroy() {
if (this.binder != null) {
this.binder.close();
this.binder = null;
}
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!this.started) {
this.started = true;
// Called when the service is started for the first time. Shields
// from multiple calls of startService(...) to invoke the code
// multiple times
onStartOnce();
}
return Service.START_STICKY;
}
}
//代理服务继承基类服务
public class ServiceProxy extends ServiceBindable {
public static final String INTENT_ACTION_PUBLISH_LASTKNOWN = "org.owntracks.android.intent.PUB_LASTKNOWN";
public static final String INTENT_ACTION_PUBLISH_LASTKNOWN_MANUAL = "org.owntracks.android.intent.PUB_LASTKNOWN_MANUAL";
public static final String INTENT_ACTION_PUBLISH_PING = "org.owntracks.android.intent.PUB_PING";
public static final String INTENT_ACTION_LOCATION_CHANGED = "org.owntracks.android.intent.LOCATION_CHANGED";
public static final String INTENT_ACTION_FENCE_TRANSITION = "org.owntracks.android.intent.FENCE_TRANSITION";
public static final String INTENT_ACTION_RECONNECT = "org.owntracks.android.intent.RECONNECT";
public static final String WAKELOCK_TAG_BROKER_PING = "org.owntracks.android.wakelock.broker.ping";
public static final String WAKELOCK_TAG_BROKER_NETWORK = "org.owntracks.android.wakelock.broker.network";
public static final String WAKELOCK_TAG_BROKER_CONNECTIONLOST = "org.owntracks.android.wakelock.broker.connectionlost";
public static final String SERVICE_APP = "1:App";
public static final String SERVICE_LOCATOR = "2:Loc";
public static final String SERVICE_BROKER = "3:Brk";
public static final String SERVICE_BEACON = "4:Bec";
public static final String KEY_SERVICE_ID = "srvID";
private static ServiceProxy instance;
private static HashMap<String, ProxyableService> services = new HashMap<String, ProxyableService>();
private static LinkedList<Runnable> runQueue = new LinkedList<Runnable>();
private static ServiceProxyConnection connection;
private static boolean bound = false;
private static boolean attemptingToBind = false;
@Override
public void onCreate() {
super.onCreate();
}
@Override
protected void onStartOnce() {
instantiateService(SERVICE_APP);
instantiateService(SERVICE_BROKER);
instantiateService(SERVICE_LOCATOR);
instantiateService(SERVICE_BEACON);
instance = this;
}
public static ServiceProxy getInstance() {
return instance;
}
@Override
public void onDestroy() {
for (ProxyableService p : services.values()) {
EventBus.getDefault().unregister(p);
p.onDestroy();
}
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int r = super.onStartCommand(intent, flags, startId); // Invokes
// onStartOnce(...)
// the fist time
// to initialize
// the service
ProxyableService s = getServiceForIntent(intent);
if (s != null)
s.onStartCommand(intent, flags, startId);
return r;
}
public static ProxyableService getService(String id) {
return services.get(id);
}
private ProxyableService instantiateService(String id) {
ProxyableService p = services.get(id);
if (p != null)
return p;
switch (id) {
case SERVICE_APP:
p = new ServiceApplication();
break;
case SERVICE_BROKER:
p = new ServiceBroker();
break;
case SERVICE_LOCATOR:
p = new ServiceLocator();
break;
case SERVICE_BEACON:
p = new ServiceBeacon();
break;
}
services.put(id, p);
p.onCreate(this);
EventBus.getDefault().registerSticky(p);
return p;
}
public static ServiceApplication getServiceApplication() {
return (ServiceApplication) getService(SERVICE_APP);
}
public static ServiceLocator getServiceLocator() {
return (ServiceLocator) getService(SERVICE_LOCATOR);
}
public static ServiceBroker getServiceBroker() {
return (ServiceBroker) getService(SERVICE_BROKER);
}
public static ServiceBeacon getServiceBeacon() {
return (ServiceBeacon) getService(SERVICE_BEACON);
}
public static ProxyableService getServiceForIntent(Intent i) {
if ((i != null) && (i.getStringExtra(KEY_SERVICE_ID) != null))
return getService(i.getStringExtra(KEY_SERVICE_ID));
else
return null;
}
public static PendingIntent getPendingIntentForService(Context c,
String targetServiceId, String action, Bundle extras) {
return getPendingIntentForService(c, targetServiceId, action, extras,
PendingIntent.FLAG_CANCEL_CURRENT);
}
public static PendingIntent getPendingIntentForService(Context c,
String targetServiceId, String action, Bundle extras, int flags) {
Intent i = new Intent().setClass(c, ServiceProxy.class);
i.setAction(action);
if (extras != null)
i.putExtras(extras);
i.putExtra(KEY_SERVICE_ID, targetServiceId);
return PendingIntent.getService(c, 0, i, flags);
}
public final static class ServiceProxyConnection implements Closeable {
private final Context context;
private final ServiceConnection serviceConnection;
private ServiceProxyConnection(Context context,
ServiceConnection serviceConnection) {
this.context = context;
this.serviceConnection = serviceConnection;
}
@Override
public void close() {
attemptingToBind = false;
if (bound) {
this.context.unbindService(this.serviceConnection);
bound = false;
}
}
public ServiceConnection getServiceConnection() {
return this.serviceConnection;
}
}
// No bind, only acting on static methods and tearing down service connection anyway
public static void closeServiceConnection() {
if ((getServiceConnection() != null) && bound)
getServiceConnection().close();
}
public static ServiceProxyConnection getServiceConnection() {
return connection;
}
public static void runOrBind(Context context, Runnable runnable) {
if ((instance != null) && (getServiceConnection() != null)) {
runnable.run();
return;
}
if (getServiceConnection() == null) {
ServiceConnection c = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
bound = false;
}
@Override
public void onServiceConnected(ComponentName name,
IBinder binder) {
Log.v("ServiceProxy", "serviceConnected, running queue");
bound = true;
attemptingToBind = false;
for (Runnable r : runQueue)
r.run();
runQueue.clear();
}
};
connection = new ServiceProxyConnection(context, c);
}
runQueue.addLast(runnable);
Log.v("ServiceProxy", "bindService called");
try {
if (!attemptingToBind) { // Prevent accidential bind during close
attemptingToBind = true;
context.bindService(new Intent(context, ServiceProxy.class), connection.getServiceConnection(), Context.BIND_AUTO_CREATE);
}
} catch (Exception e) {
Log.v("ServiceProxy", "bind exception ");
e.printStackTrace();
attemptingToBind = false;
}
}
}
//举例一个简单任务实现
public class ServiceBroker implements ProxyableService {
public enum State {
INITIAL, CONNECTING, CONNECTED, DISCONNECTING, DISCONNECTED, DISCONNECTED_USERDISCONNECT, DISCONNECTED_DATADISABLED, DISCONNECTED_ERROR
}
private static State state = State.INITIAL;
private CustomMqttClient mqttClient;
private Thread workerThread;
private LinkedList<Message> deferredPublishables;
private static Exception error;
private HandlerThread pubThread;
private Handler pubHandler;
private MqttClientPersistence persistenceStore;
private BroadcastReceiver netConnReceiver;
private BroadcastReceiver pingSender;
private ServiceProxy context;
private LinkedList<String> subscribtions;
private WakeLock networkWakelock;
private WakeLock connectionWakelock;
private String TAG_WAKELOG = "org.owntracks.android.wakelog.broker";
private ReconnectTimer reconnectTimer;
private Map<IMqttDeliveryToken, Message> sendMessages = new HashMap<>();
private final Object sendMessagesLock = new Object();
private LinkedList<Message> backlog = new LinkedList<>();
private final Object backlogLock = new Object();
@Override
public void onCreate(ServiceProxy p) {
this.context = p;
this.workerThread = null;
this.error = null;
this.subscribtions = new LinkedList<>();
this.deferredPublishables = new LinkedList<Message>();
this.pubThread = new HandlerThread("MQTTPUBTHREAD");
this.pubThread.start();
this.pubHandler = new Handler(this.pubThread.getLooper());
this.persistenceStore = new CustomMemoryPersistence();
this.reconnectTimer = new ReconnectTimer(context);
changeState(State.INITIAL);
doStart();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// doStart();
return 0;
}
private void doStart() {
doStart(false);
}
private void doStart(final boolean force) {
Thread thread1 = new Thread() {
@Override
public void run() {
handleStart(force);
if (this == ServiceBroker.this.workerThread) // Clean up worker
// thread
ServiceBroker.this.workerThread = null;
}
@Override
public void interrupt() {
if (this == ServiceBroker.this.workerThread) // Clean up worker
// thread
ServiceBroker.this.workerThread = null;
super.interrupt();
}
};
thread1.start();
}
void handleStart(boolean force) {
//Log.v(this.toString(), "handleStart: force == " + force);
if(!Preferences.canConnect()) {
//Log.v(this.toString(), "handleStart: canConnect() == false");
return;
}
// Respect user's wish to stay disconnected. Overwrite with force = true
// to reconnect manually afterwards
if ((state == State.DISCONNECTED_USERDISCONNECT)
&& !force) {
//Log.d(this.toString(), "handleStart: userdisconnect==true");
return;
}
if (isConnecting()) {
//Log.d(this.toString(), "handleStart: isConnecting == true");
return;
}
// Respect user's wish to not use data
if (!isBackgroundDataEnabled()) {
//Log.e(this.toString(), "handleStart: isBackgroundDataEnabled == false");
changeState(State.DISCONNECTED_DATADISABLED);
return;
}
// Don't do anything unless we're disconnected
if (isDisconnected()) {
//Log.v(this.toString(), "handleStart: isDisconnected() == true");
// Check if there is a data connection
if (isOnline()) {
//Log.v(this.toString(), "handleStart: isOnline() == true");
if (connect())
onConnect();
} else {
//Log.e(this.toString(), "handleStart: isDisconnected() == false");
changeState(State.DISCONNECTED_DATADISABLED);
}
} else {
//Log.d(this.toString(), "handleStart: isDisconnected() == false");
}
}
private boolean isDisconnected() {
return (state == State.INITIAL)
|| (state == State.DISCONNECTED)
|| (state == State.DISCONNECTED_USERDISCONNECT)
|| (state == State.DISCONNECTED_DATADISABLED)
|| (state == State.DISCONNECTED_ERROR)
// In some cases the internal state may diverge from the mqtt
// client state.
|| !isConnected();
}
private boolean init() {
if (this.mqttClient != null) {
return true;
}
try {
String prefix = Preferences.getTls() ? "ssl" : "tcp";
String cid = Preferences.getClientId(true);
Log.v(this.toString(), "Using client id: " + cid);
this.mqttClient = new CustomMqttClient(prefix + "://" + Preferences.getHost() + ":" + Preferences.getPort(), cid, persistenceStore, new AlarmPingSender(context));
this.mqttClient.setCallback(this);
} catch (Exception e) {
// something went wrong!
this.mqttClient = null;
changeState(e);
return false;
}
return true;
}
private static class CustomSocketFactory extends javax.net.ssl.SSLSocketFactory{
private javax.net.ssl.SSLSocketFactory factory;
public CustomSocketFactory(boolean sideloadCa) throws CertificateException, KeyStoreException, NoSuchAlgorithmException, IOException, KeyManagementException {
Log.v(this.toString(), "initializing CustomSocketFactory");
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
if(sideloadCa) {
Log.v(this.toString(), "CA sideload: true");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(new FileInputStream(Preferences.getTlsCrtPath()));
Log.v(this.toString(), "Using custom tls cert from : " + Preferences.getTlsCrtPath());
java.security.cert.Certificate ca;
try {
ca = cf.generateCertificate(caInput);
keyStore.setCertificateEntry("owntracks-custom-tls-root", ca);
} catch (Exception e) {
Log.e(this.toString(), e.toString());
} finally {
caInput.close();
}
Log.v(this.toString(), "Keystore content: ");
Enumeration<String> aliases = keyStore.aliases();
for (; aliases.hasMoreElements();) {
String o = aliases.nextElement();
Log.v(this.toString(), "Alias: " + o);
}
tmf.init(keyStore);
} else {
Log.v(this.toString(), "CA sideload: false, using system keystore");
// Use system KeyStore. This is some kind of magic.
// On devices with hardware backed keystore, one does not get a an instance of the
// system keystore when using KeyStore.getInstance("AndroidKeystore"). Instead,
// an empty keystore is returned. However, when passing null to the tmf.init method
// the system keystore is used
tmf.init((KeyStore) null);
}
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(null, tmf.getTrustManagers(), null);
this.factory= context.getSocketFactory();
}
@Override
public String[] getDefaultCipherSuites() {
return this.factory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return this.factory.getSupportedCipherSuites();
}
@Override
public Socket createSocket() throws IOException{
SSLSocket r = (SSLSocket)this.factory.createSocket();
r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
SSLSocket r = (SSLSocket)this.factory.createSocket(s, host, port, autoClose);
r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
SSLSocket r = (SSLSocket)this.factory.createSocket(host, port, localHost, localPort);
r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
SSLSocket r = (SSLSocket)this.factory.createSocket(host, port);
r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
SSLSocket r = (SSLSocket)this.factory.createSocket(address, port, localAddress,localPort);
r.setEnabledProtocols(new String[] {"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"});
return r;
}
}
private boolean connect() {
this.workerThread = Thread.currentThread(); // We connect, so we're the worker thread
changeState(State.CONNECTING);
error = null; // clear previous error on connect
if(!init()) {
return false;
}
try {
MqttConnectOptions options = new MqttConnectOptions();
setWill(options);
if (Preferences.getAuth()) {
options.setPassword(Preferences.getPassword().toCharArray());
options.setUserName(Preferences.getUsername());
}
if (Preferences.getTls()) {
options.setSocketFactory(new CustomSocketFactory(Preferences.getTlsCrtPath().length() > 0));
}
// setWill(options);
options.setKeepAliveInterval(Preferences.getKeepalive());
options.setConnectionTimeout(30);
options.setCleanSession(Preferences.getCleanSession());
this.mqttClient.connect(options);
changeState(State.CONNECTED);
return true;
} catch (Exception e) { // Catch paho and socket factory exceptions
Log.e(this.toString(), e.toString());
e.printStackTrace();
changeState(e);
return false;
}
}
private void setWill(MqttConnectOptions m) {
StringBuilder payload = new StringBuilder();
payload.append("{");
payload.append("\"_type\": ").append("\"").append("lwt").append("\"");
payload.append(", \"tst\": ")
.append("\"")
.append((int) (TimeUnit.MILLISECONDS.toSeconds(System
.currentTimeMillis()))).append("\"");
payload.append("}");
m.setWill(Preferences.getBaseTopic(), payload.toString().getBytes(), 0, false);
}
private void onConnect() {
//if (!isConnected())
// Log.e(this.toString(), "onConnect: !isConnected");
reconnectTimer.stop();
// Establish observer to monitor wifi and radio connectivity
if (this.netConnReceiver == null) {
this.netConnReceiver = new NetworkConnectionIntentReceiver();
this.context.registerReceiver(this.netConnReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
// Establish ping sender
//if (this.pingSender == null) {
// this.pingSender = new PingSender();
// this.context.registerReceiver(this.pingSender, new IntentFilter(Defaults.INTENT_ACTION_PUBLISH_PING));
//}
//scheduleNextPing();
resubscribe();
deliverBacklog();
}
public void resubscribe(){
//Log.v(this.toString(), "Resubscribing");
try {
unsubscribe();
if (Preferences.getSub())
subscribe(new String[]{Preferences.getSubTopic(true), Preferences.getBaseTopic()});
else
subscribe(new String[]{Preferences.getBaseTopic()});
} catch (MqttException e) {
e.printStackTrace();
}
}
private void subscribe(String topic) throws MqttException {
subscribe(new String[]{topic});
}
private void subscribe(String[] topics) throws MqttException{
if(!isConnected())
return;
for(String s : topics) {
Log.v(this.toString(), "Subscribing to: " + s);
}
this.mqttClient.subscribe(topics);
for (String topic : topics) {
subscribtions.push(topic);
}
}
private void unsubscribe() throws MqttException{
if(!isConnected())
return;
mqttClient.unsubscribe(subscribtions.toArray(new String[subscribtions.size()]));
subscribtions.clear();
}
public void disconnect(boolean fromUser) {
//Log.v(this.toString(), "disconnect. from user: " + fromUser);
if (isConnecting()) // throws
// MqttException.REASON_CODE_CONNECT_IN_PROGRESS
// when disconnecting while connect is in progress.
return;
try {
if (this.netConnReceiver != null) {
this.context.unregisterReceiver(this.netConnReceiver);
this.netConnReceiver = null;
}
if (this.pingSender != null) {
this.context.unregisterReceiver(this.pingSender);
this.pingSender = null;
}
} catch (Exception eee) {
Log.e(this.toString(), "Unregister failed", eee);
}
try {
if (isConnected()) {
//Log.v(this.toString(), "Disconnecting");
this.mqttClient.disconnect(0);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
this.mqttClient = null;
if (this.workerThread != null) {
this.workerThread.interrupt();
}
if (fromUser)
changeState(State.DISCONNECTED_USERDISCONNECT);
else
changeState(State.DISCONNECTED);
}
}
@Override
public void connectionLost(Throwable t) {
Log.e(this.toString(), "error: " + t.toString());
t.printStackTrace();
// we protect against the phone switching off while we're doing this
// by requesting a wake lock - we request the minimum possible wake
// lock - just enough to keep the CPU running until we've finished
if(connectionWakelock == null )
connectionWakelock = ((PowerManager) this.context.getSystemService(Context.POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, ServiceProxy.WAKELOCK_TAG_BROKER_CONNECTIONLOST);
if (!connectionWakelock.isHeld())
connectionWakelock.acquire();
if (!isOnline()) {
changeState(State.DISCONNECTED_DATADISABLED);
} else {
changeState(State.DISCONNECTED);
//scheduleNextPing();
}
reconnectTimer.start();
if(connectionWakelock.isHeld())
connectionWakelock.release();
}
public void reconnect() {
disconnect(false);
doStart(true);
}
private void changeState(Exception e) {
this.error = e;
changeState(State.DISCONNECTED_ERROR, e);
}
private void changeState(State newState) {
changeState(newState, null);
}
private void changeState(State newState, Exception e) {
//Log.d(this.toString(), "ServiceBroker state changed to: " + newState);
state = newState;
EventBus.getDefault().postSticky(
new Events.StateChanged.ServiceBroker(newState, e));
}
private boolean isOnline() {
ConnectivityManager cm = (ConnectivityManager) this.context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if(netInfo != null && netInfo.isAvailable() && netInfo.isConnected()) {
return true;
} else {
Log.e(this.toString(), "isONline == true. activeNetworkInfo: "+ (netInfo != null) +", available=" + (netInfo != null && netInfo.isAvailable()) + ", connected: " + (netInfo != null && netInfo.isConnected()));
return false;
}
}
public boolean isConnected() {
return this.mqttClient != null && this.mqttClient.isConnected( );
}
public static boolean isErrorState(State state) {
return state == State.DISCONNECTED_ERROR;
}
public static boolean hasError() {
return error != null;
}
public boolean isConnecting() {
return (this.mqttClient != null)
&& (state == State.CONNECTING);
}
private boolean isBackgroundDataEnabled() {
return isOnline();
}
@Override
public void onDestroy() {
// disconnect immediately
disconnect(false);
changeState(State.DISCONNECTED);
}
public static State getState() {
return state;
}
public static String getErrorMessage() {
if (hasError() && (error.getCause() != null))
return "Error: " + error.getCause().getLocalizedMessage();
else
return "Error: " + ServiceProxy.getInstance().getString(R.string.na);
}
public static String getStateAsString(Context c)
{
int id;
switch (getState()) {
case CONNECTED:
id = R.string.connectivityConnected;
break;
case CONNECTING:
id = R.string.connectivityConnecting;
break;
case DISCONNECTING:
id = R.string.connectivityDisconnecting;
break;
case DISCONNECTED_USERDISCONNECT:
id = R.string.connectivityDisconnectedUserDisconnect;
break;
case DISCONNECTED_DATADISABLED:
id = R.string.connectivityDisconnectedDataDisabled;
break;
case DISCONNECTED_ERROR:
id = R.string.error;
break;
default:
id = R.string.connectivityDisconnected;
}
return c.getString(id);
}
public void publish(String message, String topic, int qos, boolean retained, MessageCallbacks callback, Object extra) {
publish(new Message(topic, message, qos, retained, callback, extra));
}
public void publish(Message message, String topic, int qos, boolean retained, MessageCallbacks callback, Object extra){
message.setCallback(callback);
message.setExtra(extra);
publish(message, topic, qos, retained);
}
public void publish(Message message, String topic, int qos, boolean retained){
message.setTopic(topic);
message.setRetained(retained);
message.setQos(qos);
publish(message);
}
public void publish(final Message message) {
this.pubHandler.post(new Runnable() {
@Override
public void run() {
// This should never happen
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
Log.e("ServiceBroker", "PUB ON MAIN THREAD");
}
// Check if we can publish
if (!isOnline() || !isConnected()) {
Log.d("ServiceBroker", "publish deferred");
doStart();
return;
}
message.setPayload(message.toString().getBytes(Charset.forName("UTF-8")));
try {
if (message.getTopic() == null) {
throw new Exception("message without topic. class:" + message.getClass() + ", msg: " + message.toString());
}
IMqttDeliveryToken t = ServiceBroker.this.mqttClient.getTopic(message.getTopic()).publish(message);
message.publishing();
synchronized (sendMessagesLock) {
sendMessages.put(t, message); // if we reach this point, the previous publish did not throw an exception and the message went out
}
Log.v(this.toString(), "queued message for delivery: " + t.getMessageId());
} catch (Exception e) {
// Handle TTL for message to discard it after message.ttl publish attempts
if (message.decrementTTL() >= 1) {
synchronized (backlogLock) {
backlog.add(message);
}
message.publishQueued();
} else {
message.publishFailed();
}
e.printStackTrace();
Log.e("ServiceBroker", message + ", error:" + e.getMessage());
} finally {
}
}
});
}
public Exception getError() {
return error;
}
public Integer getDeferredPublishablesCound() {
return deferredPublishables != null ? deferredPublishables.size() : -1;
}
// After reconnecting, we try to deliver messages for which the publish previously threw an error
// Messages are removed from the backlock, a publish is attempted and if the publish the message is added at the end of the backlog again until ttl reaches zero
private void deliverBacklog() {
Iterator<Message> i = backlog.iterator();
while (i.hasNext()) {
Message m;
synchronized (backlogLock) {
m = i.next();
i.remove();
}
publish(m);
}
}
@Override
public void messageArrived(String topic, MqttMessage message)
throws Exception {
//scheduleNextPing();
String msg = new String(message.getPayload());
Log.v(this.toString(), "Received message: " + topic + " : " + msg);
String type;
JSONObject json;
try {
json = new JSONObject(msg);
type = json.getString("_type");
} catch (Exception e) {
Log.e(this.toString(), "Received invalid message: " + msg);
return;
}
if (type.equals("location")) {
LocationMessage lm = new LocationMessage(json);
EventBus.getDefault().postSticky(new Events.LocationMessageReceived(lm, topic));
} else if(type.equals("cmd") && topic.equals(Preferences.getBaseTopic()+"/cmd")) {
String action;
try {
action = json.getString("action");
} catch (Exception e) {
return;
}
switch (action) {
case "dump":
if (!Preferences.getRemoteCommandDump()) {
Log.i(this.toString(), "Dump remote command is disabled");
return;
}
ServiceProxy.getServiceApplication().dump();
break;
case "reportLocation":
if (!Preferences.getRemoteCommandReportLocation()) {
Log.i(this.toString(), "ReportLocation remote command is disabled");
return;
}
ServiceProxy.getServiceLocator().publishResponseLocationMessage();
break;
default:
Log.v(this.toString(), "Received cmd message with unsupported action (" + action + ")");
break;
}
} else if (type.equals("configuration") ) {
// read configuration message and post event only if Remote Configuration is enabled
if (!Preferences.getRemoteConfiguration()) {
Log.i(this.toString(), "Remote Configuration is disabled");
return;
}
ConfigurationMessage cm = new ConfigurationMessage(json);
EventBus.getDefault().post(new Events.ConfigurationMessageReceived(cm, topic));
} else {
Log.d(this.toString(), "Ignoring message (" + type + ") received on topic " + topic);
}
}
@Override
public void deliveryComplete(IMqttDeliveryToken messageToken) {
Log.v(this.toString(), "Delivery complete of " + messageToken.getMessageId());
Message message;
synchronized (sendMessagesLock) {
message = sendMessages.remove(messageToken);
}
message.publishSuccessful();
}
private class NetworkConnectionIntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
if(networkWakelock == null )
networkWakelock = ((PowerManager) ServiceBroker.this.context.getSystemService(Context.POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, ServiceProxy.WAKELOCK_TAG_BROKER_NETWORK);
if (!networkWakelock.isHeld())
networkWakelock.acquire();
if (isOnline() && !isConnected() && !isConnecting()) {
//Log.v(this.toString(), "NetworkConnectionIntentReceiver: triggering doStart");
doStart();
}
if(networkWakelock.isHeld());
networkWakelock.release();
}
}
public void onEvent(Events.Dummy e) {
}
// Custom blocking MqttClient that allows to specify a MqttPingSender
private static final class CustomMqttClient extends MqttClient {
public CustomMqttClient(String serverURI, String clientId, MqttClientPersistence persistence, MqttPingSender pingSender) throws MqttException {
super(serverURI, clientId, persistence);// Have to call do the AsyncClient init twice as there is no other way to setup a client with a ping sender (thanks Paho)
aClient = new MqttAsyncClient(serverURI, clientId, persistence, pingSender);
}
}
class AlarmPingSender implements MqttPingSender {
// Identifier for Intents, log messages, etc..
static final String TAG = "AlarmPingSender";
private ClientComms comms;
private Context context;
private BroadcastReceiver alarmReceiver;
private AlarmPingSender that;
private PendingIntent pendingIntent;
private volatile boolean hasStarted = false;
public AlarmPingSender(Context c ) {
Log.v(this.toString(), "AlarmPingSender instantiated");
if (c == null) {
throw new IllegalArgumentException( "Neither service nor client can be null.");
}
this.context = c;
that = this;
}
@Override
public void init(ClientComms comms) {
Log.v(this.toString(), "AlarmPingSender init");
this.comms = comms;
this.alarmReceiver = new PingTimer();
}
@Override
public void start() {
Log.v(this.toString(), "AlarmPingSender start");
context.registerReceiver(alarmReceiver, new IntentFilter(ServiceProxy.INTENT_ACTION_PUBLISH_PING));
pendingIntent = PendingIntent.getBroadcast(context, 0, new Intent(ServiceProxy.INTENT_ACTION_PUBLISH_PING), PendingIntent.FLAG_UPDATE_CURRENT);
schedule(comms.getKeepAlive());
hasStarted = true;
}
@Override
public void stop() {
Log.v(this.toString(), "AlarmPingSender stop");
// Cancel Alarm.
AlarmManager alarmManager = (AlarmManager) context.getSystemService(ServiceProxy.ALARM_SERVICE);
alarmManager.cancel(pendingIntent);
Log.d(TAG, "Unregister alarmreceiver to MqttService"+comms.getClient().getClientId());
if(hasStarted){
hasStarted = false;
try{
context.unregisterReceiver(alarmReceiver);
}catch(IllegalArgumentException e){
//Ignore unregister errors.
}
}
}
@Override
public void schedule(long delayInMilliseconds) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(ServiceProxy.ALARM_SERVICE);
// With API >= 19 Android may skew ping alarm in a window between 1000 ms and delayInMilliseconds to combine wackup with other alarms
// In lower APIs we use a fixed timestamp for wakeup
//if(Build.VERSION.SDK_INT >= 19)
// alarmManager.setWindow(AlarmManager.RTC_WAKEUP, 1000, delayInMilliseconds, pendingIntent);
// else
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delayInMilliseconds, pendingIntent);
}
class PingTimer extends BroadcastReceiver {
private WakeLock wakelock;
@Override
public void onReceive(Context context, Intent intent) {
// According to the docs, "Alarm Manager holds a CPU wake lock as
// long as the alarm receiver's onReceive() method is executing.
// This guarantees that the phone will not sleep until you have
// finished handling the broadcast.", but this class still get
// a wake lock to wait for ping finished.
long count = intent.getLongExtra(Intent.EXTRA_ALARM_COUNT, -1);
Log.d(TAG, "Ping " + count + " times.");
Log.d(TAG, "Check time :" + System.currentTimeMillis());
IMqttToken token = comms.checkForActivity();
// No ping has been sent.
if (token == null) {
return;
}
if (wakelock == null) {
PowerManager pm = (PowerManager) context.getSystemService(ServiceProxy.POWER_SERVICE);
wakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, ServiceProxy.WAKELOCK_TAG_BROKER_PING);
}
if(!wakelock.isHeld())
wakelock.acquire();
token.setActionCallback(new IMqttActionListener() {
@Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.d(TAG, "Success. Release lock(" + ServiceProxy.WAKELOCK_TAG_BROKER_PING + "):" + System.currentTimeMillis());
if(wakelock != null && wakelock.isHeld()){
wakelock.release();
}
}
@Override
public void onFailure(IMqttToken asyncActionToken,
Throwable exception) {
Log.d(TAG, "Failure. Release lock(" + ServiceProxy.WAKELOCK_TAG_BROKER_PING + "):"
+ System.currentTimeMillis());
//Release wakelock when it is done.
if(wakelock != null && wakelock.isHeld()){
wakelock.release();
}
}
});
}
}
}
class ReconnectTimer {
private PendingIntent pendingIntent;
private ReconnectReceiver alarmReceiver;
private static final int MAX_INTERVAL_MS = 60 * 60 * 1000; // 60 Minutes in miliseconds
private static final int INTERVAL_MS = 5 * 60 * 1000; // 10 minutes in miliseconds;
private int backoff = 0;
private Context context;
private boolean hasStarted;
public ReconnectTimer(Context context) {
this.context = context;
this.alarmReceiver = new ReconnectReceiver();
}
public void start() {
if(hasStarted)
return;
Log.v(this.toString(), "ReconnectTimer start");
context.registerReceiver(alarmReceiver, new IntentFilter(ServiceProxy.INTENT_ACTION_RECONNECT));
pendingIntent = PendingIntent.getBroadcast(this.context, 0, new Intent(ServiceProxy.INTENT_ACTION_RECONNECT), PendingIntent.FLAG_UPDATE_CURRENT);
schedule();
hasStarted = true;
}
public void stop() {
Log.v(this.toString(), "ReconnectTimer stop");
backoff = 0; // Reset backoff
// Cancel Alarm.
AlarmManager alarmManager = (AlarmManager) context.getSystemService(ServiceProxy.ALARM_SERVICE);
alarmManager.cancel(pendingIntent);
if (hasStarted) {
hasStarted = false;
try {
context.unregisterReceiver(alarmReceiver);
} catch (IllegalArgumentException e) {
//Ignore unregister errors.
}
}
}
/*
* Schedules a reconnect attempt between after 10, 20, 30,...60, 60, 60 minutes.
* On APIs >= 19 the OS may skew the time between 5 minutes and 10, 20, 30... minutes
* */
private void schedule() {
backoff++;
AlarmManager alarmManager = (AlarmManager) this.context.getSystemService(Context.ALARM_SERVICE);
int delayInMilliseconds = INTERVAL_MS * backoff < MAX_INTERVAL_MS ? INTERVAL_MS * backoff : MAX_INTERVAL_MS;
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delayInMilliseconds, pendingIntent);
}
class ReconnectReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.v(this.toString(), "onReceive");
doStart();
schedule();
}
}
}
private static final class CustomMemoryPersistence implements MqttClientPersistence {
private static Hashtable data;
public CustomMemoryPersistence(){
}
@Override
public void open(String s, String s2) throws MqttPersistenceException {
if(data == null) {
data = new Hashtable();
}
}
private Integer getSize(){
return data.size();
}
@Override
public void close() throws MqttPersistenceException {
}
@Override
public void put(String key, MqttPersistable persistable) throws MqttPersistenceException {
Log.v(this.toString(), "put key " + key);
data.put(key, persistable);
}
@Override
public MqttPersistable get(String key) throws MqttPersistenceException {
Log.v(this.toString(), "get key " + key);
return (MqttPersistable)data.get(key);
}
@Override
public void remove(String key) throws MqttPersistenceException {
Log.v(this.toString(), "removing key " + key);
data.remove(key);
}
@Override
public Enumeration keys() throws MqttPersistenceException {
return data.keys();
}
@Override
public void clear() throws MqttPersistenceException {
Log.v(this.toString(), "clearing store");
data.clear();
}
@Override
public boolean containsKey(String key) throws MqttPersistenceException {
return data.containsKey(key);
}
}
最后使用方法,可在任意activity中通过单例调用服务中的方法:
ServiceProxy.runOrBind(this, new Runnable() {
@Override
public void run() {
ServiceProxy.getServiceBroker().publish(new CommandMessage("reportLocation"), c.getTopic(), Preferences.getPubQos(), false, null, null);
}
});
}
- 一个android service下多任务的设计
- Android IntentService 可执行耗时任务的Service
- Android 在Service 中开启线程执行任务,Service占用的内存越来越多,怎么回事?
- 一个完整的android Service
- 设计一个类似Window的任务管理器
- 一个简单的任务执行引擎设计
- 长任务下的Swing设计,Thread
- android 后台任务的最佳实现1——Service
- Android Service,AlarmManager组合实现定时任务踩的坑
- Android任务管理器的设计实现
- android学习:service的创建,打开、关闭一个service
- Android中Service的一个Demo例子
- 如何关闭android的一个service
- Android 6.0一个完整的native service
- Android系统添加一个自己的service
- Android 实现service后台多任务下载notification进度条更新
- Android下的Service的基本用法
- Android四大组件之service(二)——用service、scheduleAtFixedRate写一个后台定时执行任务
- C#基于事件驱动的多串口多线程串口通讯软件架构设计
- ios 基本图形的绘制
- 题目1128:求平均年龄
- DLL动态链接库编程入门之二:非MFC DLL
- java 判断一个字符串是否包含另一个字符串
- 一个android service下多任务的设计
- Let it go!
- linux下使用代码向redis写数据
- DLL动态链接库编程入门之三:MFC规则DLL(上)
- JavaScript中getBoundingClientRect()方法详解
- 微信第三方登陆 与分享
- 89. PHP 使用命名空间:基础
- dp(poj1185 炮兵阵地)
- 还是畅通工程 HDU1233(最小生成树)