巧用CountDownLatch实现多线程并行工作

【前言】
      CountDownLatch是JDK提供的一个同步工具,它可以让一个或多个线程挂起等待,一直等到其他线程执行完成才会继续执行。常用方法有countDown方法和await方法,CountDownLatch在初始化时,需要指定一个整数n作为计数器。当调用countDown方法时,计数器会被减1;当调用await方法时,如果计数器大于0时,当前线程会被阻塞,一直到计数器被countDown方法减到0时,当前线程才会继续执行。计数器是无法重置的,当计数器被减到0时,调用await方法都会直接返回。

一、使用场景:扫描手机内的安装包

      由于手机文件夹目录众多,单线程遍历全部文件夹找出所有的安装包会比较费时,但是假如开启多个线程并行去搜索不同的文件夹,然后再将结果汇总起来返回,那么效率就会明显有所提高。然而开启多个线程去搜索是很容易实现,但是由于每个线程耗时不同,有些线程搜索完毕了,但是有些线程可能还在搜索,怎么确保所有的线程都搜索完毕再把结果汇总起来返回呢?这时候借助CountDownLatch这个类就能轻易地实现

二、场景案例实现

1、开启多线程遍历文件夹,这里使用线程池去实现,以便达到线程复用的目的

  /**
     * 扫描手机外存所有目录查找apk文件,需要读取外部权限
     */
    public void scanAllApks(Context context) {
        //避免多线程同时访问
        synchronized (INSTANCE){
            // 公共储存下的所有文件
            File[] files = Environment.getExternalStorageDirectory().listFiles();
            // 私有存储目录下的文件
            File filesDir = context.getExternalFilesDir(null);

            //多线程并发执行扫描
            if (executorService == null) {
                executorService = Executors.newFixedThreadPool(10);
            }

            //遍历
            List<Runnable> runnableList = new ArrayList<>();
            if (files != null) {
                List<File> fileList = new ArrayList<>();
                fileList.add(filesDir);
                fileList.addAll(Arrays.asList(files));
                for (File file : fileList) {
                    if (file.isDirectory()) {
                        //当前是目录的话,构造一个Runnale对象,稍后放到线程池去执行
                        runnableList.add(new SearchApkRunnable(file));
                    } else {
                        //当前是文件的话,尝试直接去解析
                        tryToParseApk(context, file);
                    }
                }
            }

            //初始化一个计数为文件夹总数的计数器
            countDownLatch = new CountDownLatch(runnableList.size());
            //使用线程池去执行遍历任务
            for(Runnable runnable: runnableList){
                executorService.execute(runnable);
            }

            try {
                //当前线程会堵塞在这里,直到所有线程执行完毕才会继续往下执行
                countDownLatch.await();
                Logger.i("扫描完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
                Logger.e("扫描被打断");
            }

        }
    }

2、每个线程所做的事情就是递归去查找当前目录下的所有安装包,线程执行完毕会调用CountDownLatchcountDown()方法将计数器减一

    public class SearchApkRunnable implements Runnable{
        private File mFile;
        private Context mCxt;
        public SearchApkRunnable(Contex context, File file){
            this.mFile = file;
            this.mCxt = context;
        }
        @Override
        public void run() {
            try {
                scanDirToFindApk(mCxt, this.mFile);
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                //计数减一
               countDownLatch.countDown();
            }

        }
    }


    /**
     * 扫描目录,查找apk文件
     *
     * @param dir
     */
    private void scanDirToFindApk(Context context, File dir) {
        if (Thread.currentThread().isInterrupted()) {
            Logger.d("扫描终止:"+ Thread.currentThread().getName());
            return;
        }

        File[] files = dir.listFiles();
        if (files != null && files.length > 0) {
            for (File f : files) {
                //递归查找
                if (f.isDirectory()) {
                    //扫描目录
                    scanDirToFindApk(context,f);
                } else {
                    //是文件的话,尝试解析apk信息
                    tryToParseApk(context,f);
                }
            }
        }
    }
   

3、查找发现是文件时候,若是apk后缀的文件,尝试解析出apk的信息

 /**
     * 尝试解析APk信息
     *
     * @param file
     * @return
     */
    private ApkInfo tryToParseApk(Context context, File file) {
        if (file == null) {
            return null;
        }

        String absolutePath = file.getAbsolutePath();
        //判断是apk
        if (absolutePath.endsWith(".apk")) {
            //过滤掉小于0.1M的apk
            if (file.length() >= 1024*100) {
                PackageInfo packageInfo;
                ApkInfo apkInfo;
                try {
                    PackageManager packageManager = context.getPackageManager();
                    packageInfo = packageManager.getPackageArchiveInfo(absolutePath, PackageManager.GET_ACTIVITIES);
                    if(packageInfo == null){
                        return null;
                    }
                    //一定要设置sourceDir 与 publicSourceDir,否则获取不到label与icon
                    apkInfo = createApkInfo(file, packageInfo, packageManager);
                    //加入安装包集合ArrayList中
                    APK_INFOS.add(apkInfo);
                    return apkInfo;
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }

        }

        return null;
    }
    @NonNull
    private ApkInfo createApkInfo(File file,  PackageInfo packageInfo, PackageManager packageManager) {
        ApkInfo apkInfo;
        ApplicationInfo applicationInfo = packageInfo.applicationInfo;
        if(TextUtils.isEmpty(applicationInfo.sourceDir)){
            applicationInfo.sourceDir = file.getAbsolutePath();
            applicationInfo.publicSourceDir = file.getAbsolutePath();
        }
        String appName = applicationInfo.loadLabel(packageManager).toString();
        Drawable icon = applicationInfo.loadIcon(packageManager);
        String packageName = packageInfo.packageName;
        String versionName = packageInfo.versionName;
        int versionCode = packageInfo.versionCode;
        long apkSize = file.length();
        apkInfo = new ApkInfo(appName, packageName, versionCode, versionName, icon, apkSize, file.getAbsolutePath(), file.getName());
        return apkInfo;
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值