Android(4.0.3+): Service, AsyncTask, 定时任务和UI通信

Service使用AlarmManager实现后台定时任务

在Android 4.0.3版本中, 使用AlarmManager实现后台定时任务是比较好的方案, 其实现机制, 是利用Service的 onStartCommand() 方法, 在每次被AlarmManager唤醒后, 执行任务并注册下一次唤醒到AlarmManager. 涉及的代码

1. 新建DaemonService, 实现 onStartCommand() 方法, 在这个方法中新开线程执行任务, 并再次将AlarmReceiver注册到AlarmManager. 注: 同样的注册多次调用时, 不会注册多个, 而是会进行更新. 这个方法会在Activity中调用 startService(intent); 方法时被调用.

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new DaemonThread().start();
        AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        long triggerAtTime = SystemClock.elapsedRealtime() + 5 * 1000;

        Intent i = new Intent(this, AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
        return super.onStartCommand(intent, flags, startId);
    }

 2. onDestroy() 方法在调用 Activity stopService(intent) 时会被调用, 此时需要将AlarmReceiver从AlarmManager中cancel掉.

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy");
        AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        Intent i = new Intent(this, AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
        manager.cancel(pi);
        super.onDestroy();
    }

3. 新建Receiver, 用来注册到AlarmManager, 用于将来响应Alarm消息. 在内部的onReceive方法中, 启动DaemonService

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Intent i = new Intent(context, DaemonService.class);
        context.startService(i);
    }
}

4. 在AndroidManifest.xml中添加Service和Receiver

    <application
        <!--... -->
        <service android:name=".DaemonService" ></service>
        <receiver android:name=".AlarmReceiver" ></receiver>
    </application>

5. 在Activity中, 对应控件的点击响应中添加service的启动, 停止代码

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // ...

        if (id == R.id.action_start) {
            Intent intent = new Intent(this, DaemonService.class);
            startService(intent);
            return true;
        }

        if (id == R.id.action_stop) {
            Intent intent = new Intent(this, DaemonService.class);
            stopService(intent);
            return true;
        }

        // ...

        return super.onOptionsItemSelected(item);
    }

定时任务中使用 AsyncTask和 httpUrlConnection访问网址, 使用Callback进行结果回调

1. 新建HttpAsyncCallback接口, 要接收AsyncTask返回数据的, 都要实现这个接口

public interface HttpAsyncCallback {
    // This function will be called from inside of your AsyncTask when you are ready to callback to
    // your controllers (like a fragment, for example) The object in the completionHandler will be
    // whatever it is that you need to send to your controllers
    void completionHandler(Boolean success, int type, Object object);
}

2. 新建HttpAsyncTask类, 进行实际的HTTP访问

public class HttpAsyncTask extends AsyncTask<String, Void, Void> {
    public static final int METHOD_GET = 0;
    public static final int METHOD_POST = 1;

    private static final String TAG = HttpAsyncTask.class.getSimpleName();

    private String postData;
    private int method;
    private int connectTimeout;
    private int readTimeout;
    private String encoding;

    private int type;
    private HttpAsyncCallback callback;

    public HttpAsyncTask(int method, String encoding) {
        this(null, method, encoding, 10000, 10000, 0, null);
    }

    public HttpAsyncTask(int method, String encoding, int type, HttpAsyncCallback callback) {
        this(null, method, encoding, 10000, 10000, type, callback);
    }

    public HttpAsyncTask(String postData, int method, String encoding, int type, HttpAsyncCallback callback) {
        this(postData, method, encoding, 10000, 10000, type, callback);
    }

    public HttpAsyncTask(String postData, int method, String encoding, int connectTimeout, int readTimeout, int type, HttpAsyncCallback callback) {
        this.postData = postData;
        this.method = method;
        this.encoding = encoding;
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;

        this.type = type;
        this.callback = callback;
    }

    @Override
    protected Void doInBackground(String... strings) {
        Log.d(TAG, "Timestamp:" + System.currentTimeMillis());
        HttpURLConnection connection = null;
        try {
            connection = (HttpURLConnection) new URL(strings[0]).openConnection();
            connection.setConnectTimeout(connectTimeout);
            connection.setReadTimeout(readTimeout);
            if (method == METHOD_GET) {
                connection.setRequestMethod("GET");
            } else {
                // get请求的话默认就行了,post请求需要setDoOutput(true),这个默认是false的。
                connection.setDoOutput(true);
                connection.setRequestMethod("POST");
                if (this.postData != null) {
                    OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
                    writer.write(postData);
                    writer.flush();
                }
            }

            int statusCode = connection.getResponseCode();
            if (statusCode ==  200) {
                InputStream in = connection.getInputStream();
                byte[] bytes = getBytesByInputStream(in);
                String response = new String(bytes, encoding);
                Log.d(TAG, response);
                // From here you can convert the string to JSON with whatever JSON parser you like to use
                // After converting the string to JSON, I call my custom callback. You can follow this
                // process too, or you can implement the onPostExecute(Result) method
                // Use the response to create the object you need
                if (callback != null) {
                    callback.completionHandler(true, type, "Timestamp:" + System.currentTimeMillis() + ", " + response);
                }
            } else {
                Log.d(TAG, statusCode+"");
                if (callback != null) {
                    callback.completionHandler(false, type, statusCode);
                }
            }
        } catch (IOException e) {
            Log.e(TAG, e.getMessage(), e);
        } finally {
            if (connection != null){
                connection.disconnect();
            }
        }
        return null;
    }

    private byte[] getBytesByInputStream(InputStream is) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length;
        try {
            while ((length = is.read(buffer)) != -1) {
                bos.write(buffer, 0, length);
            }
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        } finally {
            try {
                bos.close();
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }
        }
        return bos.toByteArray();
    }

    public static String formDataToString(Map<String, String> data, String encoding) {
        StringBuilder sb = new StringBuilder();
        String con = "";
        for (String key : data.keySet()) {
            String value = data.get(key);
            try {
                key = URLEncoder.encode(key, encoding);
                value = URLEncoder.encode(value, encoding);
                sb.append(con).append(key).append("=").append(value);
                con = "&";
            } catch (UnsupportedEncodingException e) {
                Log.e(TAG, "UnsupportedEncodingException " + encoding + " in processing:" + key);
            }
        }
        return sb.toString();
    }

    public static String formDataToJson(Map<String, String> data, String encoding) {
        if (data != null) {
            JSONObject jsonObject = new JSONObject(data);
            return jsonObject.toString();
        }
        return null;
    }
}

 

使用 Callback和BroadcastReceiver实现消息通信

1. 在DaemonService中实现HttpAsyncCallback接口, 用于接收HttpAsyncTask任务执行结果

public class DaemonService extends Service implements HttpAsyncCallback {
    private static final String TAG = DaemonService.class.getSimpleName();

    // ...

    @Override
    public void completionHandler(Boolean success, int type, Object object) {
        Log.d(TAG, "completionHandler");
    }
}

2. 在DaemonService的onStartCommand()方法中, 将自己做为参数传给HttpAsyncTask. 1和2是为了将AsyncTask的结果传回Service

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Map<String, String> map = new HashMap<>();
        map.put("phone", "13800138000");
        HttpAsyncTask task = new HttpAsyncTask(HttpAsyncTask.formDataToString(map, "UTF-8"), HttpAsyncTask.METHOD_POST, "UTF-8", 0, this);
        task.execute("https://www.toutiao.com/api/pc/realtime_news/");
        //new DaemonThread("").start();
        AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        long triggerAtTime = SystemClock.elapsedRealtime() + 5 * 1000;

        Intent i = new Intent(this, AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
        return super.onStartCommand(intent, flags, startId);
    }

3. 为了从Service将结果传回Fragment, 需要在Fragment中注册一个BroadcastReceiver, 实现onReceive方法, 在这个方法中将结果更新到TextView, 在onCreateView中初始化这个receiver, 在onStart和onStop方法中进行注册和取消. 注意: 从fragment中获取TextView时, 需要在onActivityCreated方法中才行, 在其他的事件方法(onCreateView, onAttach中, findViewById拿到的是null

public class MainActivityFragment extends Fragment {
    private static final String TAG = MainActivityFragment.class.getSimpleName();
    private BroadcastReceiver receiver;
    private TextView tv;

    ...

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView");
        receiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String s = intent.getStringExtra("msg");
                tv.setText(s);
            }
        };
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onAttach(Context context) {
        Log.d(TAG, "onAttach");
        super.onAttach(context);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onActivityCreated");
        super.onActivityCreated(savedInstanceState);
        tv = getActivity().findViewById(R.id.sample_text);
    }

    @Override
    public void onStart() {
        Log.d(TAG, "onStart");
        super.onStart();
        if (receiver != null) {
            IntentFilter intentFilter = new IntentFilter(MainActivityFragment.class.getName() + ".TextView");
            getActivity().registerReceiver(receiver, intentFilter);
        }
    }

    @Override
    public void onStop() {
        Log.d(TAG, "onStop");
        if (receiver != null) {
            getActivity().unregisterReceiver(receiver);
        }
        super.onStop();
    }
}

 

代码在 https://github.com/MiltonLai/android-service-example

 

posted on 2018-07-27 16:16  Milton  阅读(377)  评论(0编辑  收藏  举报

导航