編輯:關於Android編程
android開發過程中,下載是必備的功能,下載安裝包,或者下載圖片,假設用戶下載過程中人為中斷網絡,或者網絡不穩定中斷下載任務,好的用戶體驗是從斷開的地方繼續下載,而不是又從頭開始下載,因為比方說用戶是拿4g來下載的,你一個游戲安裝包100多M,用戶下載了90M,突然手機沒電了,充好電,又從頭下載,那豈不是浪費用戶的流量。所以斷點續傳是非常必要的一個功能。其實斷點續傳也可以使用多線程來實現的,本篇先不寫的這麼麻煩了,就單線程去下載一個任務了,如果中斷了,下次再點擊下載的時候,從斷點繼續下載。好,開始我們的實驗。本實驗是下載一個安裝包。比如我們下載360手機衛士。給出Demo代碼。
1、activity_resume_download.xml 下載頁面
2、ResumeDownloadActivity.java
package com.figo.study.activity; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.ProgressBar; import android.widget.Toast; import com.figo.study.R; import com.figo.study.mgr.DownloadMgr; import com.figo.study.utils.FileUtils; import java.io.File; public class ResumeDownloadActivity extends Activity implements View.OnClickListener { String tag = "ResumeDownloadActivity"; ProgressBar mProgressBar; String downloadUrl = "http://msoftdl.360.cn/mobilesafe/shouji360/360safe/500192/360MobileSafe.apk"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_resume_download); findViewById(R.id.btn_download).setOnClickListener(this); findViewById(R.id.btn_cancel).setOnClickListener(this); mProgressBar = (ProgressBar) findViewById(R.id.progressBar); } private final Handler msgHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case 0: Toast.makeText(getApplicationContext(), msg.getData().get("msg").toString(), Toast.LENGTH_SHORT).show(); break; default: break; } } }; @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_download: String directory = FileUtils.getStorageDirectory(); String fileName = directory + File.separator + getFileName(downloadUrl); DownloadMgr.getInstance().addTask(downloadUrl, fileName, new DownloadMgr.Callback() { @Override public void onProgress(long progress, long total) { super.onProgress(progress, total); mProgressBar.setProgress((int) (100 * progress / total)); } @Override public void onStart() { super.onStart(); sendMsg("start"); } @Override public void onSuccess() { super.onSuccess(); Log.i(tag, "success"); sendMsg("success"); } @Override public void onFailed(boolean cancelled, String msg) { super.onFailed(cancelled, msg); Log.e(tag, msg); //Looper.getMainLooper().prepare();//這麼干雖然可以在子線程,彈出toast,但是子線程執行到這裡,後面的代碼將不再執行 // Toast.makeText(ResumeDownloadActivity.this, "download start", Toast.LENGTH_SHORT).show(); //Looper.getMainLooper().loop(); //進程間通信還是用Handler比較靠譜 sendMsg(msg); } }); break; case R.id.btn_cancel: DownloadMgr.getInstance().cancelTask(downloadUrl); break; } } private String getFileName(String downloadUrl) { return downloadUrl.substring(downloadUrl.lastIndexOf("/")); } private void sendMsg(String msg) { Message msgNew = new Message(); msgNew.what = 0; Bundle bundle = new Bundle(); bundle.putString("msg", msg); msgNew.setData(bundle); msgHandler.sendMessage(msgNew); } }
package com.figo.study.mgr; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import com.figo.study.activity.MyApplication; import com.figo.study.utils.CommonUtil; import com.figo.study.utils.IOUtil; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.InterruptedIOException; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URL; import java.util.HashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * Created by figo on 16/7/25. */ public class DownloadMgr { private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; Executor mExecutor = Executors.newFixedThreadPool(MAXIMUM_POOL_SIZE); static DownloadMgr mDownloadMgr; static Object obj = new Object(); HashMap<String, DownloadTask> mTasks = new HashMap<String, DownloadTask>(); public static void init() { getInstance(); } public static DownloadMgr getInstance() { synchronized (obj) { if (mDownloadMgr == null) { mDownloadMgr = new DownloadMgr(); } } return mDownloadMgr; } public void addTask(String downloadUrl, String filePath, Callback callback) { if (!mTasks.containsKey(downloadUrl)) { mTasks.put(downloadUrl, new DownloadTask(downloadUrl, filePath, callback)); } mTasks.get(downloadUrl).startDownload(); } public void removeTask(String downloadUrl, String filePath, Callback callback) { if (mTasks.containsKey(downloadUrl)) { mTasks.get(downloadUrl).cancel(); } mTasks.remove(downloadUrl); } public class DownloadTask implements Runnable { private String downloadUrl; private String filePath; Callback callback; public DownloadTask(String downloadUrl, String filePath, Callback callback) { this.downloadUrl = downloadUrl; this.filePath = filePath; this.callback = callback; } public void startDownload() { mExecutor.execute(this); } @Override public void run() { runResumable(downloadUrl, filePath, callback); } synchronized boolean cancel() { if (thread == null) return false; thread.interrupt(); return true; } } Thread thread; public void runResumable(String downloadUrl, String filePath, Callback callback) { Thread.currentThread().setPriority(Thread.MIN_PRIORITY); thread = Thread.currentThread(); final Context ctx = MyApplication.getInstance(); String msg = ""; boolean interrupted = false; HttpURLConnection conn = null; long resumePosition = 0; final File file = new File(filePath); try { //20160720 add final File parent = file.getParentFile(); if (!parent.exists()) { parent.mkdirs(); } if (!file.exists()) { file.createNewFile(); } callback.onStart(); //簡單一點就用md5來校驗 // if (file.exists() && StringUtil.equalsIgnoreCase(md5, Md5.md5(file))) { // suc = true; // return; // } //非wifi環境不下載 if (!isWifiActive(ctx)) { msg = "請在wifi環境下下載"; callback.onFailed(true, msg); return; } resumePosition = file.exists() ? file.length() : 0; // Create connection object conn = (HttpURLConnection) new URL(downloadUrl).openConnection(); conn.setConnectTimeout(60000); conn.setReadTimeout(60000); conn.setDoInput(true); conn.setUseCaches(false); // Make the request conn.setRequestMethod("GET"); conn.setRequestProperty("User-Agent", "Java/Android"); conn.setRequestProperty("Connection", "close"); conn.setRequestProperty("Http-version", "HTTP/1.1"); conn.setRequestProperty("Cache-Control", "no-transform"); if (resumePosition > 0) { //斷點續傳的關鍵設置Range conn.setRequestProperty("Range", "bytes=" + resumePosition + "-"); } conn.connect(); final int responseCode = conn.getResponseCode(); if (responseCode == 416) { msg = "已經下載!"; callback.onFailed(true, msg); return; } if (responseCode != 200 && responseCode != 206) { msg = "網絡繁忙,請稍後再試!"; callback.onFailed(true, msg); return; } long fileLength = conn.getContentLength(); InputStream is = new BufferedInputStream(conn.getInputStream()); FileOutputStream fos = new FileOutputStream(file, resumePosition > 0); try { int read = 0; long progress = resumePosition; byte[] buffer = new byte[4096 * 2]; while ((read = is.read(buffer)) > 0 && !(interrupted = Thread.interrupted())) { try { fos.write(buffer, 0, read); } catch (Exception e) { msg = "磁盤空間已滿,無法下載"; throw e; } // progress progress += read; callback.onProgress(progress, fileLength); } } finally { IOUtil.closeQuietly(fos); IOUtil.closeQuietly(is); } //20160720 resumable download if (file.exists()) { //也可以通過md5來校驗 // if (StringUtil.equalsIgnoreCase(md5, Md5.md5(file))) { // suc = true; // return; // } //檢驗數據是否完整 if (file.length() == fileLength + resumePosition) { callback.onSuccess(); return; } } } catch (Exception e) { interrupted = interrupted || Thread.interrupted() || (e instanceof InterruptedIOException && !(e instanceof SocketTimeoutException)); msg = "網絡異常,下載失敗"; if (interrupted) { msg = "下載被中斷!"; } callback.onFailed(true, msg); } finally { disconnect(conn); } } static void disconnect(HttpURLConnection conn) { try { if (conn == null) return; conn.disconnect(); } catch (Throwable e) { e.printStackTrace(); } } public static abstract class Callback { public void onStart() { } public void onProgress(long progress, long total) { } public void onSuccess() { } public void onFailed(boolean cancelled, String msg) { } } public boolean isWifiActive(Context ctx) { try { ConnectivityManager mgr = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo info = mgr.getActiveNetworkInfo(); return (info != null) ? info.getType() == ConnectivityManager.TYPE_WIFI : false; } catch (Exception e) { return false; } } public void cancelAllTask() { try { if (mTasks != null) { for (String taskKey : mTasks.keySet()) { mTasks.get(taskKey).cancel(); } } } catch (Exception e) { CommonUtil.printStackTrace(e); } } public void cancelTask(String key) { try { if (mTasks != null) { mTasks.get(key).cancel(); } } catch (Exception e) { CommonUtil.printStackTrace(e); } } public static boolean checkNetAvailable(Context ctx) { try { ConnectivityManager mgr = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo info = mgr.getActiveNetworkInfo(); return (info != null) ? true : false; } catch (Exception e) { return true; } } }
onTach介紹ontach是Android系統中整個事件機制的基礎。Android中的其他事件,如onClick、onLongClick等都是以onTach為基礎的。o
寫在前面的廢話一般Android開發者都會使用Eclipse,Android studio觀察log輸出,其實後台是使用adb來打印log的,這裡介紹的是如何讓log輸出
公司的新產品上線需要添加的彈幕功能,於是花了一天時間寫了一個Demo。效果實現如下:一開始的思路是:1、首先實現一個自定義的Layout,在其中獲得需要展示的彈幕數組,每
自定義賬戶類型 Custom Account Type當有多個APP共用一個賬號系統的時候,在用戶的Android設備上創建一個自定義賬戶用以處理登錄認證會方便很多,比如