精华内容
下载资源
问答
  • Android 下载任务队列实现-Kotlin

    千次阅读 2019-09-09 13:40:48
    下载队列Android中可以说是非常常见了,常见的做法是将下载任务放到服务中,创建线程任务队列,对其进行处理,这样子问题也有很多,比如常见的线程安全之类的。并且考虑到线程创建和关闭对资源的消耗,我们还要...

    下载队列在Android中可以说是非常常见了,常见的做法是将下载任务放到服务中,创建线程任务队列,对其进行处理,这样子问题也有很多,比如常见的线程安全之类的。并且考虑到线程创建和关闭对资源的消耗,我们还要维护一个线程池。同时实现动态像队列中添加任务,采用生产者-消费者模式。非常复杂。

    今天采用kotlin来实现一个可以随时添加下载任务的下载队列。

    知识点

    • 协程

    • 通道

    • Android service

    如果对于协程和通道不明白的可以参考kotlin官方文档

    思路

    首先创建一个Android Service,onBind返回null,因为不需要绑定任何activity。重写onCreate函数,onStartCommand函数。

    class DownloadService : Service(), CoroutineScope by MainScope() {
    	override fun onBind(intent: Intent): IBinder? {
            return null
        }
        override fun onCreate() {
            super.onCreate()
        }
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        }    
    }
    

    简单说明onCreate和onStartCommand函数:

    • onCreate : 在服务首次开启时调用一次,之后不再调用
    • onStartCommand :每次调用startService()都会调用一次

    这样就非常清晰了,在onCreate方法中实现下载队列,在onStartCommand 方法中添加下载队列。

    /**
     * 声明下载通道
     */
    private val downLoadChannel = Channel<String>()
    
    /**
     * 添加下载任务
     */
    private suspend fun addDownloadTask(down: String) {
        count++
        Log.d(TAG, "增加:$down,当前任务数:$count")
        downLoadChannel.send(down)
    }
    

    我们只要在onStartCommand中调用添加下载任务就行了

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        launch {
            c++
            Log.d(TAG, "准备增加")
            addDownloadTask("下载任务$c")
        }
        return super.onStartCommand(intent, flags, startId)
    }
    

    在onCreate中,我们需要创建一个在IO线程中的协程来处理下载任务。每当任务处理完毕之后调用销毁程序关闭任务,销毁程序需要先等待11s检查是否没有任务添加了,如果是在等待5s中确认真的没有任务添加之后,关闭通道和服务进程。

    @ExperimentalCoroutinesApi
    suspend fun processingDownloadTasks() {
        while (!downLoadChannel.isEmpty) {
            val down = downLoadChannel.receive()
            Log.d(TAG, "正在下载:$down")
            // 模拟下载
            delay(1000)
            count--
        }
        waitingToStop()
    }
    
    @ExperimentalCoroutinesApi
    suspend fun waitingToStop() {
        Log.d(TAG, "等待...${downLoadChannel.isEmpty}")
        delay(11000)
        Log.d(TAG, "每11s检查是否有下载任务,当前任务:${downLoadChannel.isEmpty}")
        if (downLoadChannel.isEmpty) {
            Log.d(TAG, "没有下载任务,最后5s到底有没有,当前任务:${downLoadChannel.isEmpty}")
            delay(5000)
            Log.d(TAG, "延迟,当前任务:${downLoadChannel.isEmpty}")
            if (downLoadChannel.isEmpty) {
                Log.d(TAG, "真的没有下载任务,我要关闭了")
                downLoadChannel.cancel()
                // 停止
                stopSelf()
                return
            }
        }
        processingDownloadTasks()
    }
    
    override fun onBind(intent: Intent): IBinder? {
        return null
    }
    
    @ExperimentalCoroutinesApi
    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "启动服务")
    
        launch(Dispatchers.IO) {
            processingDownloadTasks()
        }
    }
    

    在activity中随时都可以调用startService来开启下载进程。

    startService<DownloadService>()
    

    运行效果

    启动服务
    等待...true
    准备增加
    增加:下载任务1,当前任务数:1
    准备增加
    增加:下载任务2,当前任务数:2
    准备增加
    增加:下载任务3,当前任务数:3
    准备增加
    增加:下载任务4,当前任务数:4
    每11s检查是否有下载任务,当前任务:false
    正在下载:下载任务1
    准备增加
    增加:下载任务5,当前任务数:5
    正在下载:下载任务2
    正在下载:下载任务3
    正在下载:下载任务4
    正在下载:下载任务5
    等待...true
    准备增加
    增加:下载任务6,当前任务数:1
    每11s检查是否有下载任务,当前任务:false
    正在下载:下载任务6
    等待...true
    每11s检查是否有下载任务,当前任务:true
    没有下载任务,最后5s到底有没有,当前任务:true
    延迟,当前任务:true
    真的没有下载任务,我要关闭了
    关闭服务
    
    展开全文
  • Android下载任务队列实现

    千次阅读 2012-12-13 23:56:50
    主要就是有一个线程队列,维护这些任务,这里没有用到Queue而是用List是考虑到显示的问题。线程类,任务类 package com.robert.my; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; ...

    今天有人问我这个问题了,然后就写了代码。代码加了很多注释,我就不赘述了。主要就是有一个线程队列,维护这些任务,这里没有用到Queue而是用List是考虑到显示的问题。线程类,任务类

    package com.robert.my;
    
    import java.io.BufferedInputStream;
    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    import android.os.Handler;
    import android.os.Message;
    /**
     * @author Robert
     * */
    public class DownloadThread extends Thread{
    	
    	/**开始下载*/
    	public final static int THREAD_BEGIN = 1;
    	/**下载结束*/
    	public final static int THREAD_FINISHED = 2;
    	//下载进度
    	private float percent = 0;
    	//下载路径
    	private URL url;
    	//下载的文件大小
    	private long fileLength;
    	//文件的保存路径
    	private String filePath;
    	//是否线程已启动
    	private boolean isStarted = false;
    	
    	private Handler mHandler;
    
    	
    	public DownloadThread(URL url, String filePath, Handler mHandler){
    		this.url = url;
    		this.filePath = filePath;
    		this.mHandler = mHandler;
    	}
    
    	/**开始下载任务*/
    	@Override
    	public void run() {
    		isStarted = true;
    		BufferedInputStream bis  = null;
    		BufferedOutputStream bos = null;
    		try{	
    			HttpURLConnection conn = (HttpURLConnection) url.openConnection();	//建立一个远程连接句柄,此时尚未真正连接
    			conn.setConnectTimeout(5*1000);	//设置连接超时时间为5秒
    			conn.setRequestMethod("GET");	//设置请求方式为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("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");	//设置Connection的方式
    			conn.connect();	//和远程资源建立真正的连接,但尚无返回的数据流
    			
    			fileLength = conn.getContentLength();
    			byte[] buffer = new byte[8096];		//下载的缓冲池为8KB
    			
    			bis = new BufferedInputStream(conn.getInputStream());
    			bos = new BufferedOutputStream(new FileOutputStream(new File(filePath)));
    			
    			long downloadLength = 0;//当前已下载的文件大小
    			int bufferLength = 0;   
    			
    			while(( bufferLength = bis.read(buffer) ) != -1){
    				bos.write(buffer, 0, bufferLength);
    				bos.flush();
    				
    				//计算当前下载进度
    				downloadLength += bufferLength;
    				percent = downloadLength/fileLength;
    			}
    			//发送下载完毕的消息
    			Message msg = new Message();
    			msg.what = THREAD_FINISHED;
    			mHandler.sendMessage(msg);
    			
    		}catch(Exception e){
    			e.printStackTrace();
    			//TODO:建议这里发送下载失败的消息
    		}finally{
    				try {
    					if(bis != null) {
    						bis.close();
    					}
    					if(bos != null){
    						bos.close();
    					}
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    			}
    	}
    
    	public URL getUrl() {
    		return url;
    	}
    
    
    	public float getPercent() {
    		return percent;
    	}
    	
    	
    	public boolean isStarted(){
    		return isStarted;
    	}
    	
    	public void setHandler(Handler mHandler){
    		this.mHandler = mHandler;
    	}
    
    }
    


    Activity

    package com.eric.download;
    
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    import com.robert.my.DownloadThread;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.ListView;
    import android.widget.SimpleAdapter;
    import android.widget.Toast;
    
    /**
     * @author Robert
     * */
    public class MultiActivity extends Activity{
    	//线程锁,如果对这个不懂,百度一下
    	private Lock lock = new ReentrantLock();
    	//任务集合
    	List<DownloadThread> threads = new ArrayList<DownloadThread>();
    	//任务状态的数据
    	List<Map<String,String>> data = new ArrayList<Map<String,String>>();
    
    	private EditText editText;
    	private ListView listView;
    	private Button button;
    	
    	private int threadFinishedCount = 0;//已完成任务的数量
    	private int count = 0;				//已添加多少个任务
    	
    
    	
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.mylayout);
    		initViews();
    	}
    	
    	/**
    	 * 设置任务列表的数据并刷新显示任务列表
    	 * */
    	private void showListView(){
    		SimpleAdapter adapter = new SimpleAdapter(this, data, android.R.layout.simple_list_item_2, new String[]{"count", "status"}, new int[]{
    			android.R.id.text1, android.R.id.text2	
    		});
    		listView.setAdapter(adapter);
    		listView.invalidate();
    	}
    	/**
    	 * 初始化界面元素
    	 * */
    	private void initViews() {
    		editText = (EditText)findViewById(R.id.download_edt);
    		button   = (Button)findViewById(R.id.download_btn);
    		listView = (ListView)findViewById(R.id.listview1);
    		button.setOnClickListener(new OnClickListener(){
    			public void onClick(View v) {
    				try {
    					count++;
    					
    					lock.lock();
    					threads.add(new DownloadThread(
    							new URL(editText.getText().toString()),"/mnt/sdcard/a" + count + ".html",myHandler));
    					lock.unlock();
    					
    					Map<String,String> map = new HashMap<String,String>();
    					map.put("status", "等待下载");
    					map.put("count", "线程" + count);
    					data.add(map);
    					
    				} catch (MalformedURLException e) {
    					e.printStackTrace();
    				}
    				showListView();
    				
    				Message message = new Message();
    				message.what = DownloadThread.THREAD_BEGIN;
    				myHandler.sendMessage(message);
    			}
    			
    		});
    	}
    	
    	private Handler myHandler = new Handler(){
    
    		@Override
    		public void handleMessage(Message msg) {
    			switch(msg.what){
    			case DownloadThread.THREAD_BEGIN:
    				lock.lock();
    				if(threads.size() <= count && threads.size() > threadFinishedCount){
    					if(!threads.get(threadFinishedCount).isStarted()){
    						//开始一个新的下载任务
    						threads.get(threadFinishedCount).start();
    						//设置显示当前任务状态为正在下载
    						data.get(threadFinishedCount).put("status", "下载中……");
    						
    						showListView();
    					}
    				}else{
    					Toast.makeText(MultiActivity.this, "无任务了", Toast.LENGTH_LONG);
    				}
    				lock.unlock();
    				break;
    				
    			case DownloadThread.THREAD_FINISHED:
    				lock.lock();
    				if(threads.size() >= threadFinishedCount){
    					//设置当前下载任务已完成
    					data.get(threadFinishedCount).put("status", "下载完成");
    					threadFinishedCount++;
    					//开始下一个任务
    					Message message = new Message();
    					message.what = DownloadThread.THREAD_BEGIN;
    					sendMessage(message);
    					showListView();
    				}
    				lock.unlock();
    				break;
    			}
    		}
    		
    	};
    	
    	
    }
    

    xml布局很普通,简单显示一些数据而已


    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
        
        <EditText android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/download_edt"
            android:text="@string/url"
            android:hint="输入下载地址"
            />
        <Button android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="点击下载"
            android:id="@+id/download_btn"
             />
        <ListView android:layout_width="match_parent" android:layout_height="match_parent"
            android:id="@+id/listview1"
            ></ListView>
    
    </LinearLayout>

    效果图


    展开全文
  • 前言 自己写的app中 有大量的webView 在加载的...基本思路是 长按获取图片的url 然后将url 通过IO流的方式 下载到本地 代码实现 有了思路 就好解决问题了 首先监听webView的长按事件 //WebView长按的监听 mWebView.

    前言

    自己写的app中 有大量的webView 在加载的过程中 新增了许多功能
    比如

    加载H5链接时 遇到有趣的图片 想长按保存下来是否可以呢

    答案:“肯定是可以的”

    网上有很多例子 但是很多有成功不了 不知道是我权限没给对 还是写法有问题
    总之保存不了图片到本地

    实现效果

    效果图

    思路

    基本思路是 长按获取图片的url 然后将url 通过IO流的方式 下载到本地

    代码实现

    有了思路 就好解决问题了

    首先监听webView的长按事件

           //WebView长按的监听
            mWebView.setOnLongClickListener(v -> {
                final WebView.HitTestResult hitTestResult = mWebView.getHitTestResult();
                // 如果是图片类型或者是带有图片链接的类型
                if (hitTestResult.getType() == WebView.HitTestResult.IMAGE_TYPE ||
                        hitTestResult.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
                    // 弹出保存图片的对话框
                    new AlertDialog.Builder(getActivity())
                            .setItems(new String[]{"保存图片"}, (dialog, which) -> {
                                String pic = hitTestResult.getExtra();//获取图片
                                Log.e(TAG, " 获取到的图片地址为  :" + pic);
                                MyDownloadManager myDownloadManager = new MyDownloadManager();
                                switch (which) {
                                    case 0:
                                        myDownloadManager.addDownloadTask(pic);
                                        ToastUtils.showShort(getActivity(), "保存成功");
                                        break;
                                }
                            })
                            .show();
                    return true;
                }
                return false;//保持长按可以复制文字
            });
    

    WebView长按时 ui的效果图展示
    2

    其中hitTestResult是查看图片类型和图片链接

    来看下获取到的图片 地址类型
    1

    这里是长按保存 触发的点击事件
    下面紧接着提示一个保存的状态 实际已经开始下载
    myDownloadManager.addDownloadTask(pic) 是封装好的下载类

    						switch (which) {
                                    case 0:
                                        myDownloadManager.addDownloadTask(pic);
                                        ToastUtils.showShort(getActivity(), "保存成功");
                                        break;
                                }
    

    MyDownloadManager 封装类

    MyDownloadManager 封装类 启用了线程池
    队列形式下载 保存的图片

    public class MyDownloadManager {
        public static final int EXTERNAL_STORAGE_REQ_CODE = 10;
        private static final int REQUEST_EXTERNAL_STORAGE = 1;
        private static String[] PERMISSIONS_STORAGE = {
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE};
    
        /**
         * Checks if the app has permission to write to device storage
         * <p>
         * If the app does not has permission then the user will be prompted to
         * grant permissions
         *
         * @param activity
         */
        public static void verifyStoragePermissions(Activity activity) {
            // Check if we have write permission
            int permission = ActivityCompat.checkSelfPermission(activity,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE);
    
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // We don't have permission so prompt the user
                ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,
                        REQUEST_EXTERNAL_STORAGE);
            }
    
        }
    
        private static final String TAG = "MyDownloadManager";
        private File downloadDir; // 文件保存路径
        private static MyDownloadManager instance; // 单例
    
        // 单线程任务队列
        public static Executor executor;
        private static final ThreadFactory sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);
    
            public Thread newThread(Runnable r) {
                return new Thread(r, "MyDownloadManager #" + mCount.getAndIncrement());
            }
        };
        //线程池
        private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(128);
        public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(1, 1, 1,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
    
    
        public MyDownloadManager() {
            // 初始化下载路径
            downloadDir = new File("/sdcard/");
            if (!downloadDir.exists()) {
                downloadDir.mkdirs();
            }
            executor = new SerialExecutor();
        }
    
        /**
         * 顺序执行下载任务
         */
        private static class SerialExecutor implements Executor {
            final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
            Runnable mActive;
    
            public synchronized void execute(final Runnable r) {
                mTasks.offer(() -> {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                });
                if (mActive == null) {
                    scheduleNext();
                }
            }
    
            protected synchronized void scheduleNext() {
                if ((mActive = mTasks.poll()) != null) {
                    THREAD_POOL_EXECUTOR.execute(mActive);
                }
            }
        }
    
        /**
         * 获取单例对象
         *
         * @return
         */
        public static MyDownloadManager getInstance() {
            if (instance == null) {
                instance = new MyDownloadManager();
            }
            return instance;
        }
    
        /**
         * 添加下载任务
         *
         * @param path
         */
        public void addDownloadTask(final String path) {
            Log.e(TAG, "进入run : " + path);
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG, "进入run");
                    download(path);
                }
            });
        }
    
        /**
         * 下载文件
         *
         * @param path
         */
        private void download(String path) {
            String fileName = MD5(path);
    
            File savePath = new File(downloadDir, fileName); // 下载文件路径
            //File finallyPath = new File(downloadDir, fileName + ".png"); // 下载完成后加入
            if (savePath.exists()) {
                // 文件存在则已下载
                Log.e(TAG, "file is existed");
                return;
            }
            // 如果是Wifi则开始下载
            if (savePath.exists() && savePath.delete()) { // 如果之前存在文件,证明没有下载完成,删掉重新创建
                savePath = new File(downloadDir, fileName);
                Log.e(TAG, "download 111111");
            }
            Log.e(TAG, "download start");
            try {
                byte[] bs = new byte[1024];
                int len;
                URL url = new URL(path);
                InputStream is = url.openStream();
                OutputStream os = new FileOutputStream(savePath, true);
                while ((len = is.read(bs)) != -1) {
                    os.write(bs, 0, len);
                }
                is.close();
                os.close();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        /**
         * 添加删除任务
         *
         * @param path
         */
        public void addDeleteTask(final String path) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    delete(path);
                }
            });
        }
    
        /**
         * 删除本地文件
         *
         * @param path
         */
        private void delete(String path) {
            String fileName = MD5(path);
            File savePath = new File(downloadDir, fileName + ".png");
            Log.i(TAG, savePath.getPath());
            if (savePath.exists()) {
                if (savePath.delete()) {
                    Log.i(TAG, "file is deleted");
                }
            }
        }
    
        /**
         * 返回下载路径
         *
         * @return
         */
        public File getDownloadDir() {
            return downloadDir;
        }
    
        public String getfileName(String path) {
            String f = MD5(path);
            return f;
        }
    
        public static String MD5(String path) {
            // URL url = new URL(path);
            //String url = path;
            //String path = Environment.getExternalStorageDirectory().getAbsolutePath();
            //final long startTime = System.currentTimeMillis();
            //String filename=url.substring(url.lastIndexOf("/") + 1);
    
            //设置日期格式
            SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
            String time = df.format(new Date());
            //命名图片名字
            String filename = time + ".jpg";
            return filename;
        }
    
    }
    

    权限申请

      //动态申请 读写SD卡的权限
            int permission = ActivityCompat.checkSelfPermission(getActivity(),
                    Manifest.permission.WRITE_EXTERNAL_STORAGE);
    
            if (permission != PackageManager.PERMISSION_GRANTED) {
                // 请求权限
                ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                        EXTERNAL_STORAGE_REQ_CODE);
            }
    

    如果是android10 androidQ版本
    记得在manifest中加多一句
    android:requestLegacyExternalStorage="true"

    AndroidQ 参考另一篇博文

    open failed: EACCES (Permission denied)解决方法 AndroidQ

    共勉

    2021 继续加油 ~

    展开全文
  • 多线程和队列下载

    2013-08-01 09:43:07
    android 多线程和队列下载,是一个商业模式的开发
  • 最近的项目是一个与音乐相关的App,...最初的思路便是任务队列,单线程顺序执行,一个文件接着一个文件进行下载。之前看过AsyncTask的部分源码,其设计与我的想法类似,于是便借鉴着AsyncTask的源码,实现了一个特别

    最近的项目是一个与音乐相关的App,其中有一个功能:收藏喜欢的歌曲,在wifi的环境下自动下载。

    考虑到音乐歌曲都是3、4Mb的小文件,断点下载的功能便不需要了。因此只需要实现一个特别轻量、简单的下载管理类,进行管理即可。

    最初的思路便是任务队列,单线程顺序执行,一个文件接着一个文件进行下载。

    之前看过AsyncTask的部分源码,其设计与我的想法类似,于是便借鉴着AsyncTask的源码,实现了一个特别简单、轻量的下载管理类。

    源码如下:

    public class MyDownloadManager {
    
        private static final String TAG = "MyDownloadManager";
        private File downloadDir; // 文件保存路径
        private static MyDownloadManager instance; // 单例
    
        // 单线程任务队列
        public static Executor executor;
        private static final ThreadFactory sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);
    
            public Thread newThread(Runnable r) {
                return new Thread(r, "MyDownloadManager #" + mCount.getAndIncrement());
            }
        };
        private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(128);
        public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(1, 1, 1,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
    
    
        private MyDownloadManager() {
            // 初始化下载路径
            downloadDir = new File(AndroidCacheUtils.getCacheDirFile(MiaApplication.getInstance()), "download");
            if (!downloadDir.exists()) {
                downloadDir.mkdirs();
            }
            executor = new SerialExecutor();
        }
    
        /**
         * 顺序执行下载任务
         */
        private static class SerialExecutor implements Executor {
            final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
            Runnable mActive;
    
            public synchronized void execute(final Runnable r) {
                mTasks.offer(new Runnable() {
                    public void run() {
                        try {
                            r.run();
                        } finally {
                            scheduleNext();
                        }
                    }
                });
                if (mActive == null) {
                    scheduleNext();
                }
            }
    
            protected synchronized void scheduleNext() {
                if ((mActive = mTasks.poll()) != null) {
                    THREAD_POOL_EXECUTOR.execute(mActive);
                }
            }
        }
    
        /**
         * 获取单例对象
         *
         * @return
         */
        public static MyDownloadManager getInstance() {
            if (instance == null) {
                instance = new MyDownloadManager();
            }
            return instance;
        }
    
        /**
         * 添加下载任务
         *
         * @param path
         */
        public void addDownloadTask(final String path) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    download(path);
                }
            });
        }
    
        /**
         * 下载文件
         *
         * @param path
         */
        private void download(String path) {
            String fileName = AndroidMD5.MD5(path);
            File savePath = new File(downloadDir, fileName); // 下载文件路径
            File finallyPath = new File(downloadDir, fileName + ".mp3"); // 下载完成后加入.mp3后缀
            if (finallyPath.exists()) { // 文件存在则已下载
                Log.i(TAG, "file is existed");
                return;
            }
            if (AndroidNetWorkUtils.isWifiDataEnable(MiaApplication.getInstance())) { // 如果是Wifi则开始下载
                if (savePath.exists() && savePath.delete()) { // 如果之前存在文件,证明没有下载完成,删掉重新创建
                    savePath = new File(downloadDir, fileName);
                }
                Log.i(TAG, "download start");
                try {
                    byte[] bs = new byte[1024];
                    int len;
                    URL url = new URL(path);
                    InputStream is = url.openStream();
                    OutputStream os = new FileOutputStream(savePath);
                    while ((len = is.read(bs)) != -1) {
                        os.write(bs, 0, len);
                    }
                    os.close();
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (savePath.renameTo(finallyPath)) { // 下载完成后重命名为.mp3文件
                    Log.i(TAG, "download end");
                    EventBus.getDefault().post(new DownloadDoneEvent(path));
                }
            } else { // 不是wifi则不下载
                Log.i(TAG, "not wifi net, stop download");
            }
    
        }
    
        /**
         * 添加删除任务
         *
         * @param path
         */
        public void addDeleteTask(final String path) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    delete(path);
                }
            });
        }
    
        /**
         * 删除本地文件
         *
         * @param path
         */
        private void delete(String path) {
            String fileName = AndroidMD5.MD5(path);
            File savePath = new File(downloadDir, fileName + ".mp3");
            Log.i(TAG, savePath.getPath());
            if (savePath.exists()) {
                if (savePath.delete()) {
                    Log.i(TAG, "file is deleted");
                }
            }
        }
    
        /**
         * 返回下载路径
         *
         * @return
         */
        public File getDownloadDir() {
            return downloadDir;
        }
    }

    我们看到public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(1, 1, 1,TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);,这句代码便是创建一个线程池。其方法源码及参数说明:

    /**
         * Creates a new {@code ThreadPoolExecutor} with the given initial
         * parameters and default rejected execution handler.
         *
         * @param corePoolSize the number of threads to keep in the pool, even
         *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
         * @param maximumPoolSize the maximum number of threads to allow in the
         *        pool
         * @param keepAliveTime when the number of threads is greater than
         *        the core, this is the maximum time that excess idle threads
         *        will wait for new tasks before terminating.
         * @param unit the time unit for the {@code keepAliveTime} argument
         * @param workQueue the queue to use for holding tasks before they are
         *        executed.  This queue will hold only the {@code Runnable}
         *        tasks submitted by the {@code execute} method.
         * @param threadFactory the factory to use when the executor
         *        creates a new thread
         * @throws IllegalArgumentException if one of the following holds:<br>
         *         {@code corePoolSize < 0}<br>
         *         {@code keepAliveTime < 0}<br>
         *         {@code maximumPoolSize <= 0}<br>
         *         {@code maximumPoolSize < corePoolSize}
         * @throws NullPointerException if {@code workQueue}
         *         or {@code threadFactory} is null
         */
        public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory) {
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 threadFactory, defaultHandler);
        }

    这里我前三个参数传的都是1,既是最多只有1个线程。sPoolWorkQueue参数则是一个容量为128的任务队列,既最多能存放128个任务。

    下面我们看到SerialExecutor的代码,它有一个Runnable队列mTasks ,不断的接受Runnable对象,并通过poll操作,每次取出顶部的Runnable进行执行。结合创建的单一线程池,便实现了我需要的简易、轻量的下载器。

    展开全文
  • android线程队列(一)线程阻塞

    千次阅读 2018-09-25 21:26:25
    我们的程序因为在子线程里面做耗时操作,内存...下载小图片(6张1M以下)的效果图(2M的图程序会崩溃): 运行出来大致是这种效果 FATAL EXCEPTION: Thread-2 Process: com.example.administrator.testz, PID: 89...
  • 效果图:   1、activity_main.xml  描述:定义了六个按钮   <?xml version="1.0" encoding="utf-8"?...LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" and...
  • Demo代码下载地址 简单的单线程队列 – 工作的时候遇到劣质打印机。给打印机发消息,打印机就会打印,如果在打印机还在打印的时候,就 再发消息打印,就会出现消息丢失。所以需要给上一个任务一些处理的间隔时间. 单...
  • 这是项目中遇到的一个问题,需要解析一个xml文件,得到要下载的文件列表,根据该文件列表来下载数据包。刚开始一直想用使用for、while等流程控制语句。但导致的结构就是开启了多个线程。可是,实际上只能开启一个...
  • 多线程下载、上传文件肯定是依靠线程池管理的,这篇文章插曲介绍一下线程池。 /** * 创建线程池 * 参数1:核心线程数量 * 参数2:线程池最大数量 * 参数3:线程存活时间 * 参数4:设置时间类型 * 参数5:给每...
  • 最全的 Android 技术栈 内容涵盖绝大部分 Android 程序员所需要的技能:「数据结构算法」「程序架构」「设计模式」「性能优化」「组件化」「插件化」「热修复」「NDK技术」「自定义View」「性能优化」「Android源码...
  • android异步下载mp3文件

    2012-11-15 08:23:38
    android的异步下载文件,即在非ui主线程之外,用新的线程下载mp3文件。达到ui主线程正常运行的状态下,异步下载文件。 考虑的实现方式有两种:1.在使用HandlerThread绑定handler,并在handler处理消息队列的...
  • FFmpeg-M3U8-Android FFmpeg下载M3U8流视频 1、队列下载 2、单程下载
  • 一、Bitmap 复用池、 二、弱引用 Bitmap 内存释放、 三、从 Bitmap 复用池中获取对应可以被复用的 Bitmap 对象、 1、Android 2.3.3(API 级别 10)及以下的版本、 2、Android 4.4(API 级别 19)...五、源码及资源下载
  • android 关于多任务下载问题

    千次阅读 2013-08-27 14:59:57
    近期项目中,遇到一个问题,列表数据中的图片地址是一个需要下载JS再解析的字段,之前的图片下载是一个异步的过程,由一个队列处理。   Android系统以不同寻常的方式处理多个应用程序的同时运行...
  • Windows 环境下载 Android 源码 Android 反编译指南 Android 多渠道打包最佳实践 Android Studio 3.x 升级指南 一篇文章了解相见恨晚的 Android Binder 进程间通讯 一篇文章看明白 Android 系统启动时都干了什么 ...
  • 在网络情况不好的情况下,优酷androidclient下载视频会终止,用户放弃下载点击 删除该任务以后,切换到网络好的情况下进行下载,会显示该视频已在下载队列里,然后clientUI界面却什么都看不到。导致用户根本无法下载...
  • 在网络情况不好的情况下,优酷android客户端下载视频会终止,用户放弃下载点击 删除该任务以后,切换到网络好的情况下进行下载,会显示该视频已在下载队列里,然后客户端UI界面却什么都看不到。导致用户根本无法下载...
  • * 1>当第一次启动IntentService时,Android容器 * 将会创建IntentService对象。 * 2>IntentService将会在工作线程中轮循消息队列, * 执行每个消息对象中的业务逻辑。 * 3>如果消息队列中依然有消息,则继续...

空空如也

空空如也

1 2 3 4 5 ... 13
收藏数 260
精华内容 104
关键字:

android队列下载