package licola.demo.com.huabandemo.Service; import android.app.Activity; import android.app.IntentService; import android.app.Notification; import android.app.NotificationManager; import android.content.Intent; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import java.io.File; import java.util.HashMap; import java.util.Map; import licola.demo.com.huabandemo.API.HttpsAPI.DownUpAPI; import licola.demo.com.huabandemo.API.OnProgressResponseListener; import licola.demo.com.huabandemo.Base.HuaBanApplication; import licola.demo.com.huabandemo.Entity.DownloadInfo; import licola.demo.com.huabandemo.HttpUtils.RetrofitDownClient; import licola.demo.com.huabandemo.Util.FileUtils; import licola.demo.com.huabandemo.Util.IntentUtils; import licola.demo.com.huabandemo.Util.Logger; import licola.demo.com.huabandemo.Util.NotificationUtils; import licola.demo.com.huabandemo.Util.Utils; import okhttp3.ResponseBody; import rx.Subscriber; import rx.functions.Func1; /** * Created by LiCola on 2016/05/13 22:02 * DownloadService 后台下载服务模块 * 具有两个线程 * DownloadThread线性负责单线程下载 * notifyThread线程负责轮询接收下载进度 发出通知 生命周期依赖下载进程 */ public class DownloadService extends IntentService { private static final String TAG = "DownloadService"; private static final String KEYURL = "KeyUrl"; private static final String KEYTYPE = "KeyType"; private static final int MSG_START = 0; private static final int MSG_LOADING = 1; private static final int MSG_COMPLETE = 2; private static final OnProgressResponseListener mListener = new OnProgressResponseListener() { @Override public void onResponseProgress(long bytesRead, long contentLength, boolean done) { Logger.d(Thread.currentThread().toString() + " " + bytesRead + "/" + contentLength + " done=" + done); mDownloadInfo.mProcess = (int) (bytesRead * 100 / contentLength); } }; private long mDelayedTime = 1000; private Map<Integer, DownloadInfo> mMap = new HashMap<>(); private static DownloadInfo mDownloadInfo; private NotifyHandler mNotifyHandler; private HandlerThread mHandlerThread; //// TODO: 2016/5/18 0018 目前存在内存泄露状态 因为OnProgressResponseListener在Service中实例化 然后弃用 但是okhttp的生命周期比较长 //哪为什么不在 Handler中实例化 然后和线程一起弃用? private final class NotifyHandler extends Handler { //下载操作不频繁 可以当做类变量 使用时候再创建 private NotificationManager mNotificationManager; public NotifyHandler(Looper looper) { super(looper); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } @Override public void handleMessage(Message msg) { DownloadInfo downloadInfo = (DownloadInfo) msg.obj; int notifyId = downloadInfo.mWorkId; Notification notification; Logger.d("what=" + msg.what + " obj=" + downloadInfo.toString()); switch (msg.what) { case MSG_START: notification = NotificationUtils.showNotification( getApplication(), downloadInfo.fileName, downloadInfo.mState ); break; case MSG_LOADING: notification = NotificationUtils.showProcessNotification (getApplication(), downloadInfo.fileName, downloadInfo.mProcess); break; case MSG_COMPLETE: Intent intent = IntentUtils.startImageFile(downloadInfo.mFile, downloadInfo.mMediaType);//使用工具类 包装打开文件的intent notification = NotificationUtils.showIntentNotification( getApplication(), intent, downloadInfo.fileName, downloadInfo.mState ); break; default: notification = NotificationUtils.showNotification( getApplication(), "错误", "无法处理接受的消息" ); break; } mNotificationManager.notify(notifyId, notification); } } private Runnable mRunnable = new Runnable() { @Override public void run() { Logger.d(Thread.currentThread().toString() + " mProcess=" + mDownloadInfo.mProcess); Message message = mNotifyHandler.obtainMessage(); message.what = MSG_LOADING; message.obj = mDownloadInfo; mNotifyHandler.sendMessage(message); mNotifyHandler.postDelayed(this, mDelayedTime); } }; public DownloadService() { super(TAG); Logger.d(TAG); } public static void launch(Activity activity, String url, String type) { Intent intent = new Intent(activity, DownloadService.class); intent.putExtra(KEYURL, url); intent.putExtra(KEYTYPE, type); activity.startService(intent); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Logger.d(TAG); //线程不阻塞的回调 能够得到每次的发起操作回调 而不会阻塞 String url = intent.getStringExtra(KEYURL); String type = intent.getStringExtra(KEYTYPE); String filename = String.valueOf(System.currentTimeMillis()) + Utils.getPinsType(type); int mWorkId = url.hashCode(); //根据参数 构造对象 DownloadInfo mDownloadInfo = new DownloadInfo(filename, type, mWorkId, url, DownloadInfo.StateStart); Logger.d(mWorkId + " " + Thread.currentThread().toString()); //根据url的hashcode 放入 mMap.put(mWorkId, mDownloadInfo); sendNotifyMessage(mDownloadInfo); return super.onStartCommand(intent, flags, startId); } private void sendNotifyMessage(DownloadInfo mDownloadInfo) { Message message = mNotifyHandler.obtainMessage(); message.what = MSG_START; message.obj = mDownloadInfo; mNotifyHandler.sendMessage(message); } @Override protected void onHandleIntent(Intent intent) { //处理队列中的消息 顺序调用 处理完一个再处理下一个 //这里是线程阻塞方法 刚好可以好判断当前任务 //从map中取出构造的好的 对象 开始任务 String url = intent.getStringExtra(KEYURL); Logger.d(url.hashCode() + " " + Thread.currentThread().toString()); mDownloadInfo = mMap.get(url.hashCode()); actionDownload(url, mDownloadInfo, mListener); } @Override public void onCreate() { super.onCreate(); Logger.d(TAG); //构造HandlerThread 负责发送通知栏消息的线程 mHandlerThread = new HandlerThread("notifyThread"); mHandlerThread.start(); mNotifyHandler = new NotifyHandler(mHandlerThread.getLooper()); } /** * 阻塞当前线程的下载方法 * * @param mImageUrl */ private void actionDownload(String mImageUrl, DownloadInfo DownloadInfo, OnProgressResponseListener listener) { RetrofitDownClient.createService(DownUpAPI.class, listener) .httpDownImage(mImageUrl) //IntentService 有内部变量HandlerThread 运行在子线程中 所以不用切换线程 // .subscribeOn(Schedulers.io()) // .observeOn(Schedulers.io()) .map(new Func1<ResponseBody, File>() { @Override public File call(ResponseBody responseBody) { Logger.d(responseBody.contentLength() + " " + responseBody.contentType().toString()); // mDownloadInfo.mMediaType = responseBody.contentType().toString(); File file = FileUtils.getDirsFile();//构造目录文件 Logger.d(file.getPath()); return FileUtils.writeResponseBodyToDisk(file, responseBody, mDownloadInfo.fileName); } }) .filter(file -> file != null) .subscribe(new Subscriber<File>() { @Override public void onStart() { super.onStart(); //开始下载 线程开始轮询 mNotifyHandler.postDelayed(mRunnable, mDelayedTime); } @Override public void onCompleted() { Logger.d(); } @Override public void onError(Throwable e) { Logger.d(e.toString()); } @Override public void onNext(File file) { Logger.d(file.getAbsolutePath()); Logger.d(Thread.currentThread().toString()); mNotifyHandler.removeCallbacks(mRunnable); sendFileNotifyMessage(file, DownloadInfo); } }); } private void sendFileNotifyMessage(File file, DownloadInfo mDownloadInfo) { mDownloadInfo.mFile = file; mDownloadInfo.mState = "下载完成"; Message message = mNotifyHandler.obtainMessage(); message.what = MSG_COMPLETE; message.obj = mDownloadInfo; mNotifyHandler.sendMessage(message); } @Override public void onStart(Intent intent, int startId) { Logger.d(TAG); super.onStart(intent, startId); } @Override public void onDestroy() { super.onDestroy(); Logger.d(TAG + " " + Thread.currentThread().toString()); // mListener = null; // mDownloadInfo = null; mHandlerThread.quit();//结束轮询 HuaBanApplication.getRefwatcher(this).watch(this); } }