package org.aisen.weibo.sina.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import org.aisen.android.common.context.GlobalContext;
import org.aisen.android.common.utils.ActivityHelper;
import org.aisen.android.common.utils.KeyGenerator;
import org.aisen.android.common.utils.Logger;
import org.aisen.android.common.utils.SystemUtils;
import org.aisen.android.component.bitmaploader.BitmapLoader;
import org.aisen.android.network.http.Params;
import org.aisen.android.network.task.TaskException;
import org.aisen.android.network.task.WorkTask;
import org.aisen.android.ui.activity.basic.BaseActivity;
import org.aisen.weibo.sina.R;
import org.aisen.weibo.sina.base.AppContext;
import org.aisen.weibo.sina.base.AppSettings;
import org.aisen.weibo.sina.service.notifier.OfflineNotifier;
import org.aisen.weibo.sina.sinasdk.SinaSDK;
import org.aisen.weibo.sina.sinasdk.bean.Group;
import org.aisen.weibo.sina.sinasdk.bean.PicUrls;
import org.aisen.weibo.sina.sinasdk.bean.StatusContent;
import org.aisen.weibo.sina.sinasdk.bean.StatusContents;
import org.aisen.weibo.sina.sinasdk.bean.WeiBoUser;
import org.aisen.weibo.sina.support.bean.OfflinePictureBean;
import org.aisen.weibo.sina.support.utils.AisenUtils;
import org.aisen.weibo.sina.ui.activity.base.MainActivity;
import org.aisen.weibo.sina.ui.fragment.timeline.TimelineMainFragment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 离线数据的服务
*
* Created by wangdan on 15/5/3.
*/
public class OfflineService extends Service {
public static final String TAG = "Offline-Service";
public static final String ACTION_TOGGLE = "org.aisen.weibo.sina.ACTION_TOGGLE";// 开始离线
public static final String ACTION_STOP = "org.aisen.weibo.sina.ACTION_STOP";// 停止离线
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "OfflineTask #" + mCount.getAndIncrement());
}
};
private static Executor OFFLINE_EXECUTOR;
// 开始离线,根据分组
public static void startOffline(ArrayList<Group> groups) {
Logger.d(TAG, "开始离线,离线%d个分组", groups.size());
if (groups == null || groups.size() == 0)
return;
Intent intent = new Intent(GlobalContext.getInstance(), OfflineService.class);
intent.setAction(ACTION_TOGGLE);
intent.putExtra("groups", groups);
GlobalContext.getInstance().startService(intent);
}
public static void stopOffline() {
if (getInstance() != null) {
Intent intent = new Intent(GlobalContext.getInstance(), OfflineService.class);
intent.setAction(ACTION_STOP);
GlobalContext.getInstance().startService(intent);
}
}
public static OfflineService getInstance() {
if (mServiceRef != null)
return mServiceRef.get();
return null;
}
public static WeakReference<OfflineService> mServiceRef;
public enum OfflineStatus {
init, prepare, loadStatus, loadPicture, cancel, finished
}
// 当前服务状态
private OfflineStatus mStatus = OfflineStatus.init;
private List<Group> mGroups;// 当次离线任务的分组
private List<Group> unOfflineGroups;
private int offlineStatusCount;// 离线的微博数量
private Map<String, String> mPictureMap = new HashMap<>();// 用来去重
private LinkedBlockingQueue<OfflinePictureBean> mPictures = new LinkedBlockingQueue<>();// 线程安全队列
private int offlinePictureSize = 0;
private int offlinePictureCount = 0;// 离线的图片数量
private long offlineStatusLengh = 0;// 离线的微博总流量大小
private long offlinePictureLengh = 0;// 离线的图片总流量大小
private OfflineNotifier mNotifier;
private WeiBoUser loggedIn;
@Override
public void onCreate() {
super.onCreate();
mNotifier = new OfflineNotifier(this);
loggedIn = AppContext.getAccount().getUser();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null || TextUtils.isEmpty(intent.getAction()))
return super.onStartCommand(intent, flags, startId);
String action = intent.getAction();
if (ACTION_TOGGLE.equalsIgnoreCase(action)) {
if (mStatus == OfflineStatus.init) {
mGroups = (ArrayList<Group>) intent.getSerializableExtra("groups");
prepareOffline();
}
else {
Logger.d(TAG, "正在离线,忽略这次请求");
}
}
else if (ACTION_STOP.equalsIgnoreCase(action)) {
if (OFFLINE_EXECUTOR != null)
((ExecutorService) OFFLINE_EXECUTOR).shutdownNow();
mStatus = OfflineStatus.cancel;
if (offlinePictureCount > 0 && mNotifier != null)
mNotifier.notifyPictureSuccess(offlinePictureCount, offlinePictureLengh);
stopSelf();
}
mServiceRef = new WeakReference<OfflineService>(this);
return super.onStartCommand(intent, flags, startId);
}
// 准备离线
private void prepareOffline() {
mStatus = OfflineStatus.prepare;
mNotifier.cancelNotification(OfflineNotifier.OfflineStatus);
mNotifier.cancelNotification(OfflineNotifier.OfflinePicture);
// 开始离线微博
unOfflineGroups = new ArrayList<>();
unOfflineGroups.addAll(mGroups);
for (Group group : mGroups) {
new LoadStatusTask(group).executeOnSerialExecutor();
}
}
private synchronized void preparePicture() {
// 等微博离线完了再离线图片
if (unOfflineGroups.size() > 0)
return;
// 正在运行
if (BaseActivity.getRunningActivity() instanceof MainActivity) {
TimelineMainFragment.sendBroadcast();
}
else {
setOfflineFinished(loggedIn, true);
}
// 清理缓存数据
mPictureMap.clear();
// 微博更新完了
mNotifier.notifyStatusSuccess(mGroups.size(), offlineStatusCount, offlineStatusLengh);
// 只有WIFI情况才离线图片
if (isCanceled()) {
return;
}
// 没有图片
if (mPictures.size() == 0) {
stopSelf();
}
else {
// 开始下载队列里的图片
if (OFFLINE_EXECUTOR == null) {
OFFLINE_EXECUTOR = Executors.newFixedThreadPool(AppSettings.offlinePicTaskSize(), sThreadFactory);
}
// 新建线程队列,开始下载图片
List<LoadPictureTask> taskList = new ArrayList<>();
int taskSize = AppSettings.offlinePicTaskSize() > mPictures.size() ? mPictures.size() : AppSettings.offlinePicTaskSize();
for (int i = 0; i < taskSize; i++) {
OfflinePictureBean bean = pollPicture();
if (bean != null)
taskList.add(new LoadPictureTask(bean));
else {
break;
}
}
for (LoadPictureTask task : taskList) {
task.executeOnExecutor(OFFLINE_EXECUTOR);
}
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
class LoadStatusTask extends WorkTask<Void, Void, Boolean> {
Group group;
LoadStatusTask(Group group) {
this.group = group;
Logger.d(TAG, "开始离线分组%s", group.getName());
mNotifier.notifyStatus(group, offlineStatusLengh);
}
@Override
public Boolean workInBackground(Void... p) throws TaskException {
// 发生异常了重复加载几次
int repeat = 3;
// 离线的大小
int count = AppSettings.getOfflineStatusSize();
// String max_id = null;
while (--repeat >= 0) {
if (isCanceled())
break;
try {
Params params = new Params();
params.addParameter("list_id", group.getIdstr());
// if (!TextUtils.isEmpty(max_id))
// params.addParameter("max_id", max_id);
params.addParameter("count", String.valueOf(count));
// 这里会自动的清理现有缓存,更新新的缓存
StatusContents statusContents = SinaSDK.getInstance(AppContext.getAccount().getAccessToken()).friendshipGroupsTimeline(params);
if (statusContents.getStatuses().size() > 0) {
Logger.d(TAG, "分组%s当次加载微博%d条,节省流量%s", group.getName(), count, AisenUtils.getUnit(statusContents.getLength()));
TimelineMainFragment.clearLastRead(group, loggedIn);
// 统计流量
offlineStatusLengh += statusContents.getLength();
// 统计数量
offlineStatusCount += statusContents.getStatuses().size();
// 处理微博的图片
List<OfflinePictureBean> pictureList = new ArrayList<>();
for (StatusContent status : statusContents.getStatuses()) {
// 处理微博头像
WeiBoUser user = status.getUser();
if (user != null) {
if (!mPictureMap.containsKey(KeyGenerator.generateMD5(AisenUtils.getUserPhoto(user))) &&
!BitmapLoader.getInstance().getCacheFile(AisenUtils.getUserPhoto(user)).exists()) {
OfflinePictureBean picture = new OfflinePictureBean();
picture.setThumb(AisenUtils.getUserPhoto(user));
pictureList.add(picture);
mPictureMap.put(KeyGenerator.generateMD5(AisenUtils.getUserPhoto(user)), AisenUtils.getUserPhoto(user));
}
}
// 转发微博头像
if (status.getRetweeted_status() != null && status.getRetweeted_status().getUser() != null) {
user = status.getRetweeted_status().getUser();
if (!mPictureMap.containsKey(KeyGenerator.generateMD5(AisenUtils.getUserPhoto(user))) &&
!BitmapLoader.getInstance().getCacheFile(AisenUtils.getUserPhoto(user)).exists()) {
OfflinePictureBean picture = new OfflinePictureBean();
picture.setThumb(AisenUtils.getUserPhoto(user));
pictureList.add(picture);
mPictureMap.put(KeyGenerator.generateMD5(AisenUtils.getUserPhoto(user)), AisenUtils.getUserPhoto(user));
}
}
// 微博配图
if (status.getRetweeted_status() != null)
status = status.getRetweeted_status();
if (status.getPic_urls() != null && status.getPic_urls().length > 0) {
for (PicUrls picUrl : status.getPic_urls()) {
OfflinePictureBean picture = new OfflinePictureBean();
picture.setThumb(picUrl.getThumbnail_pic());
if (!mPictureMap.containsKey(KeyGenerator.generateMD5(picture.getThumb()))
&& !BitmapLoader.getInstance().getCacheFile(picture.getThumb()).exists()) {
pictureList.add(picture);
mPictureMap.put(KeyGenerator.generateMD5(picture.getThumb()), picture.getThumb());
}
}
}
}
// 放到图片队列
mPictures.addAll(pictureList);
offlinePictureSize = mPictures.size();
Logger.d(TAG, "分组%s新增%d张待下载图片", group.getName(), pictureList.size());
}
else {
Logger.d(TAG, "分组%s加载0条微博", group.getName());
}
return true;
} catch (Exception e) {
}
}
return false;
}
@Override
protected void onSuccess(Boolean result) {
super.onSuccess(result);
// 更新广播
if (result) {
mNotifier.notifyStatus(group, offlineStatusLengh);
}
}
@Override
protected void onFinished() {
super.onFinished();
unOfflineGroups.remove(group);
preparePicture();
}
}
int taskCount = 0;
int taskRunningCount = 0;
class LoadPictureTask extends WorkTask<Void, Void, Void> {
OfflinePictureBean bean;
LoadPictureTask(OfflinePictureBean bean) {
this.bean = bean;
taskCount++;
// Logger.d(TAG, "图片线程数%d", taskCount);
}
@Override
public Void workInBackground(Void... params) throws TaskException {
taskRunningCount++;
// Logger.d(TAG, "图片运行线程数%d", taskRunningCount);
// 下载缩略图
String url = bean.getThumb();// .replace("thumbnail", "bmiddle");
downloadPicture(url, bean);
// 2015-07-07 新增中图离线选项
if (AppSettings.offlineMidPic()) {
String mid_url = url.replace("thumbnail", "bmiddle");
downloadPicture(mid_url, bean);
}
offlinePictureLengh += bean.getLength();
return null;
}
private void downloadPicture(String url, OfflinePictureBean mBean) throws TaskException {
File file = BitmapLoader.getInstance().getCacheFile(url);
File fileTemp = new File(file.getPath() + ".tmp");
if (!file.exists()) {
int repeat = 3;
while(--repeat > 0) {
if (isCanceled())
break;
try {
// Logger.v(TAG, "开始离线图片 ---> %s", url);
Request request = new Request.Builder().url(url).build();
Response response = GlobalContext.getOkHttpClient().newCall(request).execute();
InputStream in = response.body().byteStream();
FileOutputStream out = new FileOutputStream(fileTemp);
// 获取图片数据
byte[] buffer = new byte[1024 * 8];
int readLen = -1;
while ((readLen = in.read(buffer)) != -1) {
out.write(buffer, 0, readLen);
}
out.flush();
in.close();
out.close();
fileTemp.renameTo(file);
mBean.setLength(mBean.getLength() + file.length());
// Logger.d(TAG, "离线图片成功,url = %s", url);
repeat = 0;
break;
} catch (Exception e) {
e.printStackTrace();
if (repeat == 1) {
Logger.e(TAG, "离线图片失败" + e.getMessage() + "" + e);
throw new TaskException("");
}
}
}
// if (mBean.getLength() > 0)
// Logger.v(TAG, "离线图片成功,url = %s", url);
}
else {
mBean.setLength(mBean.getLength() + file.length());
}
}
@Override
protected void onFinished() {
super.onFinished();
offlinePictureCount++;
taskCount--;
taskRunningCount--;
if (!isCanceled())
notifyPictureProgress();
if (!isCanceled()) {
OfflinePictureBean bean = pollPicture();
if (bean != null) {
new LoadPictureTask(bean).executeOnExecutor(OFFLINE_EXECUTOR);
}
else {
if (taskCount == 0) {
stopSelf();
}
}
}
stopSelfIfCan();
}
}
NotificationCompat.Builder progressNuilder;
long refreshTime = 0;
public void notifyPictureProgress() {
if (refreshTime == 0)
refreshTime = System.currentTimeMillis();
if (System.currentTimeMillis() - refreshTime >= 500 || offlinePictureCount == offlinePictureSize) {
refreshTime = System.currentTimeMillis();
String title = String.format("正在离线图片");
if (progressNuilder == null) {
progressNuilder = new NotificationCompat.Builder(this);
progressNuilder.setSmallIcon(R.drawable.statusbar_ic_send_success)
.setContentTitle(title)
.setOnlyAlertOnce(true);
}
progressNuilder.setContentInfo(String.format("%s/%s", String.valueOf(offlinePictureCount), String.valueOf(offlinePictureSize)));
progressNuilder.setProgress(Math.round(offlinePictureSize), Math.round(offlinePictureCount), false);
mNotifier.notify(OfflineNotifier.OfflinePicture, 0, progressNuilder);
}
}
private synchronized OfflinePictureBean pollPicture() {
return mPictures.poll();
}
public interface OfflineLength {
public void setLength(long length);
}
private boolean isCanceled() {
return mStatus == OfflineStatus.cancel || mStatus == OfflineStatus.finished;
}
private boolean stopSelfIfCan() {
if (isCanceled()) {
stopSelf();
return true;
}
if (SystemUtils.getNetworkType(this) != SystemUtils.NetWorkType.wifi) {
stopSelf();
mStatus = OfflineStatus.cancel;
return true;
}
return false;
}
public OfflineStatus getStatus() {
return mStatus;
}
@Override
public void onDestroy() {
super.onDestroy();
if (mStatus == OfflineStatus.cancel) {
mNotifier.notifyPictureSuccess(offlinePictureCount, offlinePictureLengh);
} else {
mNotifier.notifyPictureSuccess(offlinePictureSize, offlinePictureLengh);
}
mStatus = OfflineStatus.finished;
Logger.d(TAG, "离线服务停止");
if (MainActivity.getInstance() != null) {
MainActivity.getInstance().invalidateOptionsMenu();
}
}
public static void setOfflineFinished(WeiBoUser user, boolean finished) {
ActivityHelper.putBooleanShareData(GlobalContext.getInstance(), user.getIdstr() + "Offline_finished", finished);
}
public static boolean isOfflineFinished(WeiBoUser user) {
return ActivityHelper.getBooleanShareData(GlobalContext.getInstance(), user.getIdstr() + "Offline_finished", false);
}
}