Android 多任务多线程断点下载

移动开发 Android
很多时候我们需要在Android设备上下载远程服务器上的文件进安装,前两天晚上我看到一个视频,从网上搜了一下资料,大概理解一下.直接通过Android提供的Http类访问远程服务器,这里AndroidHttpClient是SDK 2.2中新出的方法,

让我们看一下代码的实现方法。

package com.smart.db;   
import java.util.HashMap;   
import java.util.Map;   
import android.content.Context;   
import android.database.Cursor;   
import android.database.sqlite.SQLiteDatabase;   
/**   
* 业务bean   
*   
*/   
public class FileService {   
private DBOpenHelper openHelper;   
public FileService(Context context) {   
  openHelper = new DBOpenHelper(context);   
}   
/**   
  * 获取每条线程已经下载的文件长度   
  * @param path   
  * @return   
  */   
public Map<Integer, Integer> getData(String path){   
  SQLiteDatabase db = openHelper.getReadableDatabase();   
  Cursor cursor = db.rawQuery("select threadid, downlength from SmartFileDownlog where downpath=?", new String[]{path});   
  Map<Integer, Integer> data = new HashMap<Integer, Integer>();   
  while(cursor.moveToNext()){   
   data.put(cursor.getInt(0), cursor.getInt(1));   
  }   
  cursor.close();   
  db.close();   
  return data;   
}   
/**   
  * 保存每条线程已经下载的文件长度   
  * @param path   
  * @param map   
  */   
public void save(String path,  Map<Integer, Integer> map){//int threadid, int position   
  SQLiteDatabase db = openHelper.getWritableDatabase();   
  db.beginTransaction();   
  try{   
   for(Map.Entry<Integer, Integer> entry : map.entrySet()){   
    db.execSQL("insert into SmartFileDownlog(downpath, threadid, downlength) values(?,?,?)",   
      new Object[]{path, entry.getKey(), entry.getValue()});   
   }   
   db.setTransactionSuccessful();   
  }finally{   
   db.endTransaction();   
  }   
  db.close();   
}   
/**   
  * 实时更新每条线程已经下载的文件长度   
  * @param path   
  * @param map   
  */   
public void update(String path, Map<Integer, Integer> map){   
  SQLiteDatabase db = openHelper.getWritableDatabase();   
  db.beginTransaction();   
  try{   
   for(Map.Entry<Integer, Integer> entry : map.entrySet()){   
    db.execSQL("update SmartFileDownlog set downlength=? where downpath=? and threadid=?",   
      new Object[]{entry.getValue(), path, entry.getKey()});   
   }   
   db.setTransactionSuccessful();   
  }finally{   
   db.endTransaction();   
  }   
  db.close();   
}   
/**   
  * 当文件下载完成后,删除对应的下载记录   
  * @param path   
  */   
public void delete(String path){   
  SQLiteDatabase db = openHelper.getWritableDatabase();   
  db.execSQL("delete from SmartFileDownlog where downpath=?", new Object[]{path});   
  db.close();   
}   
   
}   
package com.smart.impl;   
import java.io.File;   
import java.io.RandomAccessFile;   
import java.net.HttpURLConnection;   
import java.net.URL;   
import java.util.LinkedHashMap;   
import java.util.Map;   
import java.util.UUID;   
import java.util.concurrent.ConcurrentHashMap;   
import java.util.regex.Matcher;   
import java.util.regex.Pattern;   
import android.content.Context;   
import android.util.Log;   
import com.smart.db.FileService;   
/**   
* 文件下载器   
* @author lihuoming@sohu.com   
*/   
public class SmartFileDownloader {   
private static final String TAG = "SmartFileDownloader";   
private Context context;   
private FileService fileService;   
/* 已下载文件长度 */   
private int downloadSize = 0;   
/* 原始文件长度 */   
private int fileSize = 0;   
/* 线程数 */   
private SmartDownloadThread[] threads;   
/* 本地保存文件 */   
private File saveFile;   
/* 缓存各线程下载的长度*/   
private Map<Integer, Integer> data = new ConcurrentHashMap<Integer, Integer>();   
/* 每条线程下载的长度 */   
private int block;   
/* 下载路径  */   
private String downloadUrl;   
/**   
  * 获取线程数   
  */   
public int getThreadSize() {   
  return threads.length;   
}   
/**   
  * 获取文件大小   
  * @return   
  */   
public int getFileSize() {   
  return fileSize;   
}   
/**   
  * 累计已下载大小   
  * @param size   
  */   
protected synchronized void append(int size) {   
  downloadSize += size;   
}   
/**   
  * 更新指定线程最后下载的位置   
  * @param threadId 线程id   
  * @param pos 最后下载的位置   
  */   
protected void update(int threadId, int pos) {   
  this.data.put(threadId, pos);   
}   
/**   
  * 保存记录文件   
  */   
protected synchronized void saveLogFile() {   
  this.fileService.update(this.downloadUrl, this.data);   
}   
/**   
  * 构建文件下载器   
  * @param downloadUrl 下载路径   
  * @param fileSaveDir 文件保存目录   
  * @param threadNum 下载线程数   
  */   
public SmartFileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) {   
  try {   
   this.context = context;   
   this.downloadUrl = downloadUrl;   
   fileService = new FileService(this.context);   
   URL url = new URL(this.downloadUrl);   
   if(!fileSaveDir.exists()) fileSaveDir.mkdirs();   
   this.threads = new SmartDownloadThread[threadNum];        
   HttpURLConnection conn = (HttpURLConnection) url.openConnection();   
   conn.setConnectTimeout(5*1000);   
   conn.setRequestMethod("GET");   
   conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");   
   conn.setRequestProperty("Accept-Language", "zh-CN");   
   conn.setRequestProperty("Referer", downloadUrl);   
   conn.setRequestProperty("Charset", "UTF-8");   
   conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");   
   conn.setRequestProperty("Connection", "Keep-Alive");   
   conn.connect();   
   printResponseHeader(conn);   
   if (conn.getResponseCode()==200) {   
    this.fileSize = conn.getContentLength();//根据响应获取文件大小   
    if (this.fileSize <= 0) throw new RuntimeException("Unkown file size ");         
    String filename = getFileName(conn);   
    this.saveFile = new File(fileSaveDir, filename);/* 保存文件 */   
    Map<Integer, Integer> logdata = fileService.getData(downloadUrl);   
    if(logdata.size()>0){   
     for(Map.Entry<Integer, Integer> entry : logdata.entrySet())   
      data.put(entry.getKey(), entry.getValue());   
    }   
    this.block = (this.fileSize % this.threads.length)==0? this.fileSize / this.threads.length : this.fileSize / this.threads.length + 1;   
    if(this.data.size()==this.threads.length){   
     for (int i = 0; i < this.threads.length; i++) {   
      this.downloadSize += this.data.get(i+1);   
     }   
     print("已经下载的长度"+ this.downloadSize);   
    }      
   }else{   
    throw new RuntimeException("server no response ");   
   }   
  } catch (Exception e) {   
   print(e.toString());   
   throw new RuntimeException("don't connection this url");   
  }   
}   
/**   
  * 获取文件名   
  */   
private String getFileName(HttpURLConnection conn) {   
  String filename = this.downloadUrl.substring(this.downloadUrl.lastIndexOf('/') + 1);   
  if(filename==null || "".equals(filename.trim())){//如果获取不到文件名称   
   for (int i = 0;; i++) {   
    String mine = conn.getHeaderField(i);   
    if (mine == null) break;   
    if("content-disposition".equals(conn.getHeaderFieldKey(i).toLowerCase())){   
     Matcher m = Pattern.compile(".*filename=(.*)").matcher(mine.toLowerCase());   
     if(m.find()) return m.group(1);   
    }   
   }   
   filename = UUID.randomUUID()+ ".tmp";//默认取一个文件名   
  }   
  return filename;   
}    
/**   
  *  开始下载文件   
  * @param listener 监听下载数量的变化,如果不需要了解实时下载的数量,可以设置为null   
  * @return 已下载文件大小   
  * @throws Exception   
  */   
public int download(SmartDownloadProgressListener listener) throws Exception{   
  try {   
   RandomAccessFile randOut = new RandomAccessFile(this.saveFile, "rw");   
   if(this.fileSize>0) randOut.setLength(this.fileSize);   
   randOut.close();   
   URL url = new URL(this.downloadUrl);   
   if(this.data.size() != this.threads.length){   
    this.data.clear();//清除数据   
    for (int i = 0; i < this.threads.length; i++) {   
     this.data.put(i+1, 0);   
    }   
   }   
   for (int i = 0; i < this.threads.length; i++) {   
    int downLength = this.data.get(i+1);   
    if(downLength < this.block && this.downloadSize<this.fileSize){ //该线程未完成下载时,继续下载         
     this.threads = new SmartDownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1);   
     this.threads.setPriority(7);   
     this.threads.start();   
    }else{   
     this.threads = null;   
    }   
   }   
   this.fileService.save(this.downloadUrl, this.data);   
   boolean notFinish = true;//下载未完成   
   while (notFinish) {// 循环判断是否下载完毕   
    Thread.sleep(900);   
    notFinish = false;//假定下载完成   
    for (int i = 0; i < this.threads.length; i++){   
     if (this.threads != null && !this.threads.isFinish()) {   
      notFinish = true;//下载没有完成   
      if(this.threads.getDownLength() == -1){//如果下载失败,再重新下载   
       this.threads = new SmartDownloadThread(this, url, this.saveFile, this.block, this.data.get(i+1), i+1);   
       this.threads.setPriority(7);   
       this.threads.start();   
      }   
     }   
    }      
    if(listener!=null) listener.onDownloadSize(this.downloadSize);   
   }   
   fileService.delete(this.downloadUrl);   
  } catch (Exception e) {   
   print(e.toString());   
   throw new Exception("file download fail");   
  }   
  return this.downloadSize;   
}   
/**   
  * 获取Http响应头字段   
  * @param http   
  * @return   
  */   
public static Map<String, String> getHttpResponseHeader(HttpURLConnection http) {   
  Map<String, String> header = new LinkedHashMap<String, String>();   
  for (int i = 0;; i++) {   
   String mine = http.getHeaderField(i);   
   if (mine == null) break;   
   header.put(http.getHeaderFieldKey(i), mine);   
  }   
  return header;   
}   
/**   
  * 打印Http头字段   
  * @param http   
  */   
public static void printResponseHeader(HttpURLConnection http){   
  Map<String, String> header = getHttpResponseHeader(http);   
  for(Map.Entry<String, String> entry : header.entrySet()){   
   String key = entry.getKey()!=null ? entry.getKey()+ ":" : "";   
   print(key+ entry.getValue());   
  }   
}   
//打印日志   
private static void print(String msg){   
  Log.i(TAG, msg);   
}    
}   
package com.smart.impl;   
import java.io.File;   
import java.io.InputStream;   
import java.io.RandomAccessFile;   
import java.net.HttpURLConnection;   
import java.net.URL;   
import android.util.Log;   
public class SmartDownloadThread extends Thread {   
private static final String TAG = "SmartDownloadThread";   
private File saveFile;   
private URL downUrl;   
private int block;   
/* *下载开始位置  */   
private int threadId = -1;   
private int downLength;   
private boolean finish = false;   
private SmartFileDownloader downloader;   
public SmartDownloadThread(SmartFileDownloader downloader, URL downUrl, File saveFile, int block, int downLength, int threadId) {   
  this.downUrl = downUrl;   
  this.saveFile = saveFile;   
  this.block = block;   
  this.downloader = downloader;   
  this.threadId = threadId;   
  this.downLength = downLength;   
}   
@Override   
public void run() {   
  if(downLength < block){//未下载完成   
   try {   
    HttpURLConnection http = (HttpURLConnection) downUrl.openConnection();   
    http.setConnectTimeout(5 * 1000);   
    http.setRequestMethod("GET");   
    http.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");   
    http.setRequestProperty("Accept-Language", "zh-CN");   
    http.setRequestProperty("Referer", downUrl.toString());   
    http.setRequestProperty("Charset", "UTF-8");   
    int startPos = block * (threadId - 1) + downLength;//开始位置   
    int endPos = block * threadId -1;//结束位置   
    http.setRequestProperty("Range", "bytes=" + startPos + "-"+ endPos);//设置获取实体数据的范围   
    http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");   
    http.setRequestProperty("Connection", "Keep-Alive");       
    InputStream inStream = http.getInputStream();   
    byte[] buffer = new byte[1024];   
    int offset = 0;   
    print("Thread " + this.threadId + " start download from position "+ startPos);   
    RandomAccessFile threadfile = new RandomAccessFile(this.saveFile, "rwd");   
    threadfile.seek(startPos);   
    while ((offset = inStream.read(buffer, 0, 1024)) != -1) {   
     threadfile.write(buffer, 0, offset);   
     downLength += offset;   
     downloader.update(this.threadId, downLength);   
     downloader.saveLogFile();   
     downloader.append(offset);   
    }   
    threadfile.close();   
    inStream.close();      
    print("Thread " + this.threadId + " download finish");   
    this.finish = true;   
   } catch (Exception e) {   
    this.downLength = -1;   
    print("Thread "+ this.threadId+ ":"+ e);   
   }   
  }   
}   
private static void print(String msg){   
  Log.i(TAG, msg);   
}   
/**   
  * 下载是否完成   
  * @return   
  */   
public boolean isFinish() {   
  return finish;   
}   
/**   
  * 已经下载的内容大小   
  * @return 如果返回值为-1,代表下载失败   
  */   
public long getDownLength() {   
  return downLength;   
}   
}   
package com.smart.activoty.download;   
import java.io.File;   
import android.app.Activity;   
import android.os.Bundle;   
import android.os.Environment;   
import android.os.Handler;   
import android.os.Message;   
import android.view.View;   
import android.widget.Button;   
import android.widget.EditText;   
import android.widget.ProgressBar;   
import android.widget.TextView;   
import android.widget.Toast;   
import com.smart.impl.SmartDownloadProgressListener;   
import com.smart.impl.SmartFileDownloader;   
public class SmartDownloadActivity extends Activity {   
    private ProgressBar downloadbar;   
    private EditText pathText;   
    private TextView resultView;   
    private Handler handler = new Handler(){   
  @Override//信息   
  public void handleMessage(Message msg) {   
   switch (msg.what) {   
   case 1:   
    int size = msg.getData().getInt("size");   
    downloadbar.setProgress(size);   
    float result = (float)downloadbar.getProgress()/ (float)downloadbar.getMax();   
    int p = (int)(result*100);   
    resultView.setText(p+"%");   
    if(downloadbar.getProgress()==downloadbar.getMax())   
     Toast.makeText(SmartDownloadActivity.this, R.string.success, 1).show();   
    break;   
   case -1:   
    Toast.makeText(SmartDownloadActivity.this, R.string.error, 1).show();   
    break;   
   }   
      
  }        
    };       
    @Override   
    public void onCreate(Bundle savedInstanceState) {   
        super.onCreate(savedInstanceState);   
        setContentView(R.layout.main);   
           
        Button button = (Button)this.findViewById(R.id.button);   
        downloadbar = (ProgressBar)this.findViewById(R.id.downloadbar);   
        pathText = (EditText)this.findViewById(R.id.path);   
        resultView = (TextView)this.findViewById(R.id.result);   
        button.setOnClickListener(new View.OnClickListener() {      
   @Override   
   public void onClick(View v) {   
    String path = pathText.getText().toString();   
    if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){   
     File dir = Environment.getExternalStorageDirectory();//文件保存目录   
     download(path, dir);   
    }else{   
     Toast.makeText(SmartDownloadActivity.this, R.string.sdcarderror, 1).show();   
    }   
   }   
  });   
    }   
    //对于UI控件的更新只能由主线程(UI线程)负责,如果在非UI线程更新UI控件,更新的结果不会反映在屏幕上,某些控件还会出错   
    private void download(final String path, final File dir){   
     new Thread(new Runnable() {   
   @Override   
   public void run() {   
    try {   
     SmartFileDownloader loader = new SmartFileDownloader(SmartDownloadActivity.this, path, dir, 3);   
     int length = loader.getFileSize();//获取文件的长度   
     downloadbar.setMax(length);   
     loader.download(new SmartDownloadProgressListener(){   
      @Override   
      public void onDownloadSize(int size) {//可以实时得到文件下载的长度   
       Message msg = new Message();   
       msg.what = 1;   
       msg.getData().putInt("size", size);         
       handler.sendMessage(msg);   
      }});   
    } catch (Exception e) {   
     Message msg = new Message();//信息提示   
     msg.what = -1;   
     msg.getData().putString("error", "下载失败");//如果下载错误,显示提示失败!   
     handler.sendMessage(msg);   
    }   
   }   
  }).start();//开始         
    }   
}   
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.
  • 400.
  • 401.
  • 402.
  • 403.
  • 404.
  • 405.
  • 406.
  • 407.
  • 408.
  • 409.
  • 410.
  • 411.
  • 412.
  • 413.
  • 414.
  • 415.
  • 416.
  • 417.
  • 418.
  • 419.
  • 420.
  • 421.
  • 422.
  • 423.
  • 424.
  • 425.
  • 426.
  • 427.
  • 428.
  • 429.
  • 430.
  • 431.
  • 432.
  • 433.
  • 434.
  • 435.
  • 436.
  • 437.
  • 438.
  • 439.
  • 440.
  • 441.
  • 442.
  • 443.
  • 444.
  • 445.
  • 446.
  • 447.
  • 448.
  • 449.
  • 450.
  • 451.
  • 452.
  • 453.
  • 454.
  • 455.
  • 456.
  • 457.
  • 458.
  • 459.
  • 460.
  • 461.
  • 462.
  • 463.
  • 464.
  • 465.
  • 466.
  • 467.
  • 468.
  • 469.
  • 470.

【编辑推荐】

Android智能手机操作系统

Android开发之旅:Android架构

Android Activity和Intent机制学习笔记

责任编辑:zhaolei 来源: 网络转载
相关推荐

2015-02-03 15:06:23

android多线程下载

2009-07-17 17:29:13

多任务多线程

2013-08-13 14:39:29

多任务下载

2022-04-14 11:44:25

LiteOS线程鸿蒙

2023-08-02 09:29:40

任务池TaskPool

2023-08-01 16:35:48

鸿蒙ArkUI应用开发

2010-02-26 17:47:07

2015-11-18 18:56:36

Java多线程处理

2014-05-09 12:59:26

iOS移动互联网

2014-06-18 10:41:31

Android多任务机制

2009-08-13 09:07:36

Java多线程

2014-12-31 15:42:21

Android多线程软件下载

2012-12-25 11:39:20

Pythoncrawler

2022-09-28 15:34:06

机器学习语音识别Pytorch

2011-07-18 14:23:40

iPhone 多任务

2015-06-17 10:41:50

2021-10-13 09:33:26

Python 多任务进程

2021-09-09 07:16:00

C#多线程开发

2023-11-07 07:13:31

推荐系统多任务学习

2012-05-18 13:26:11

HTC
点赞
收藏

51CTO技术栈公众号