Android的ListView异步下载图片官方提供的源码

本文详细介绍了如何优化图片加载过程,通过改进BitmapFactory.decodeStream方法,避免其已知的bug,并实现异步下载以提高性能。包括使用FilterInputStream进行流缓冲、创建异步下载任务、并发处理与资源管理等关键步骤。


源文:

http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html


一个图片的下载体

static Bitmap downloadBitmap(String url) {
  final AndroidHttpClient client = AndroidHttpClient.newInstance("Android");
  final HttpGet getRequest = new HttpGet(url);

  try {
    HttpResponse response = client.execute(getRequest);
    final int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode != HttpStatus.SC_OK) { 
      Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url); 
      return null;
    }
    
    final HttpEntity entity = response.getEntity();
    if (entity != null) {
      InputStream inputStream = null;
      try {
        inputStream = entity.getContent(); 
        final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
        return bitmap;
      } finally {
        if (inputStream != null) {
          inputStream.close(); 
        }
        entity.consumeContent();
      }
    }
  } catch (Exception e) {
    // Could provide a more explicit error message for IOException or IllegalStateException
    getRequest.abort();
    Log.w("ImageDownloader", "Error while retrieving bitmap from " + url, e.toString());
  } finally {
    if (client != null) {
      client.close();
    }
  }
  return null;
}

作者说 BitmapFactory.decodeStream 是有bug的,怎样fixed呢??

static class FlushedInputStream extends FilterInputStream {
  public FlushedInputStream(InputStream inputStream) {
    super(inputStream);
  }

  @Override
  public long skip(long n) throws IOException {
    long totalBytesSkipped = 0L;
    while (totalBytesSkipped < n) {
      long bytesSkipped = in.skip(n - totalBytesSkipped);
      if (bytesSkipped == 0L) {
         int byte = read();
         if (byte < 0) {
           break; // we reached EOF
         } else {
           bytesSkipped = 1; // we read one byte
         }
     }
      totalBytesSkipped += bytesSkipped;
    }
    return totalBytesSkipped;
  }
}
异步下载
public class ImageDownloader {

  public void download(String url, ImageView imageView) {
      BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
      task.execute(url);
    }
  }

  /* class BitmapDownloaderTask, see below */
}

class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
  private String url;
  private final WeakReference<ImageView> imageViewReference;

  public BitmapDownloaderTask(ImageView imageView) {
    imageViewReference = new WeakReference<ImageView>(imageView);
  }

  @Override
  // Actual download method, run in the task thread
  protected Bitmap doInBackground(String... params) {
    // params comes from the execute() call: params[0] is the url.
    return downloadBitmap(params[0]);
  }

  @Override
  // Once the image is downloaded, associates it to the imageView
  protected void onPostExecute(Bitmap bitmap) {
    if (isCancelled()) {
      bitmap = null;
    }

    if (imageViewReference != null) {
      ImageView imageView = imageViewReference.get();
      if (imageView != null) {
        imageView.setImageBitmap(bitmap);
      }
    }
  }
}

处理并发(Handling concurrency)

static class DownloadedDrawable extends ColorDrawable {
  private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;

  public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
    super(Color.BLACK);
    bitmapDownloaderTaskReference =
      new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
  }

  public BitmapDownloaderTask getBitmapDownloaderTask() {
    return bitmapDownloaderTaskReference.get();
  }
}

public void download(String url, ImageView imageView) {
  if (cancelPotentialDownload(url, imageView)) {
    BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
    DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
    imageView.setImageDrawable(downloadedDrawable);
    task.execute(url, cookie);
  }
}

private static boolean cancelPotentialDownload(String url, ImageView imageView) {
  BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);

  if (bitmapDownloaderTask != null) {
    String bitmapUrl = bitmapDownloaderTask.url;
    if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
      bitmapDownloaderTask.cancel(true);
    } else {
      // The same URL is already being downloaded.
      return false;
    }
  }
  return true;
}

private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
  if (imageView != null) {
    Drawable drawable = imageView.getDrawable();
    if (drawable instanceof DownloadedDrawable) {
      DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
      return downloadedDrawable.getBitmapDownloaderTask();
    }
  }
  return null;
}

private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
  if (imageView != null) {
    Drawable drawable = imageView.getDrawable();
    if (drawable instanceof DownloadedDrawable) {
      DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
      return downloadedDrawable.getBitmapDownloaderTask();
    }
  }
  return null;
}

if (imageViewReference != null) {
  ImageView imageView = imageViewReference.get();
  BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
  // Change bitmap only if this process is still associated with it
  if (this == bitmapDownloaderTask) {
    imageView.setImageBitmap(bitmap);
  }
}



<think>我们参考用户提供的引用内容,用户希望了解如何使用PyInstallerNuitkaPython脚本打包成Windows可执行文件(.exe)。根据引用内容,我们可以总结如下: 1. 引用[1]和[3]介绍了PyInstaller使用方法,包括安装和基本打包命令。 2. 引用[2]介绍了Nuitka的安装和基本打包方法。 3. 引用[4]给出了一个使用PyInstaller打包PyQt5应用的例子。 因此,我们将分别介绍PyInstallerNuitka使用步骤,并提供常见选项的解释。 ### 一、使用PyInstaller打包 #### 1. 安装PyInstaller 使用pip安装,建议使用国内镜像源加速: ```bash pip install pyinstaller -i https://pypi.tuna.tsinghua.edu.cn/simple ``` #### 2. 基本打包命令 - 打包为单个exe文件(控制台程序): ```bash pyinstaller -F your_script.py ``` - 打包为单个exe文件(无控制台窗口,适用于GUI程序): ```bash pyinstaller -F -w your_script.py ``` #### 3. 常用选项说明 | 选项 | 全称 | 作用 | |------|------|------| | `-F` | `--onefile` | 打包成一个单独的exe文件 | | `-D` | `--onedir` | 创建一个目录,包含exe文件以及依赖文件(默认) | | `-w` | `--windowed`, `--noconsole` | 使用窗口,无控制台(适用于GUI程序) | | `-c` | `--console`, `--nowindowed` | 使用控制台,无界面(默认) | | `-i` | `--icon` | 指定exe的图标,例如:`-i icon.ico` | #### 4. 示例:打包PyQt5应用 假设你有一个使用PyQt5编写的GUI程序,并且希望打包成一个exe文件,不显示控制台窗口,并指定图标: ```bash pyinstaller -F -w -i myicon.ico your_qt_app.py ``` #### 5. 打包后的文件位置 打包完成后,exe文件位于项目目录下的`dist`文件夹中。 ### 二、使用Nuitka打包 #### 1. 安装Nuitka ```bash pip install nuitka -i https://pypi.tuna.tsinghua.edu.cn/simple ``` #### 2. 基本打包命令 最简单的打包方式(生成一个可执行文件和一个依赖文件夹): ```bash nuitka --standalone your_script.py ``` #### 3. 常用选项 | 选项 | 作用 | |------|------| | `--standalone` | 创建一个独立的文件夹,包含所有依赖 | | `--onefile` | 打包成单个exe文件(需要安装`zstandard`包) | | `--windows-disable-console` | 打包为无控制台窗口(用于GUI程序) | | `--output-dir=out` | 指定输出目录 | | `--enable-plugin=pyqt5` | 启用PyQt5插件(如果使用PyQt5) | | `--windows-icon-from-ico=icon.ico` | 设置exe图标 | #### 4. 示例:打包为单个exe文件 首先,确保安装了`zstandard`: ```bash pip install zstandard ``` 然后打包: ```bash nuitka --standalone --onefile --windows-disable-console --windows-icon-from-ico=icon.ico your_script.py ``` #### 5. 打包后的文件位置 - 使用`--standalone`(默认)会在当前目录生成一个`your_script.dist`文件夹,其中包含可执行文件和所有依赖。 - 使用`--onefile`则会在当前目录生成一个单独的exe文件(在`your_script.dist`文件夹中可能也会有一个,但使用`--onefile`时,直接生成在输出目录中,具体取决于输出目录设置)。 ### 三、对比与选择 | 工具 | 优点 | 缺点 | |------|------|------| | PyInstaller | 简单易用,支持多种选项,社区支持好 | 打包文件较大,启动速度相对较慢 | | Nuitka | 生成C/C++代码,性能更高,打包文件较小 | 配置相对复杂,编译时间较长 | ### 四、常见问题解决 1. **PyInstaller打包后文件过大?** 使用UPX压缩(下载UPX并添加到环境变量,PyInstaller会自动使用)[^3]。 2. **Nuitka打包时缺少依赖?** 使用`--include-module`手动包含缺失的模块,或检查插件是否启用(如PyQt5需要`--enable-plugin=pyqt5`)。 3. **打包后运行时提示找不到文件?** 如果脚本中使用了相对路径读取文件,打包后路径会改变。建议使用以下方式获取绝对路径: ```python import sys import os if getattr(sys, 'frozen', False): base_path = sys._MEIPASS else: base_path = os.path.abspath(".") file_path = os.path.join(base_path, "your_file.txt") ``` ### 五、进阶建议 - 对于复杂项目,建议使用虚拟环境打包,避免依赖冲突。 - 使用图标时,确保图标文件(.ico)与脚本在同一目录或使用绝对路径指定。 §§相关问题§§ 1. 如何减小PyInstaller打包生成的exe文件大小? 2. 使用Nuitka打包时如何包含数据文件(如图片、配置文件)? 3. 如何为PyQt5应用打包添加版本信息? 4. 打包后的程序如何实现管理员权限运行(UAC)?</think>### 使用 PyInstallerNuitka 打包 Python 脚本为 Windows EXE 文件 #### 一、PyInstaller 打包方法(推荐简单项目) 1. **安装 PyInstaller** ```bash pip install pyinstaller -i https://pypi.tuna.tsinghua.edu.cn/simple ``` 2. **基础打包命令** ```bash # 打包单个EXE文件(控制台程序pyinstaller -F your_script.py # 打包窗口程序(无控制台) pyinstaller -F -w your_script.py # 添加自定义图标 pyinstaller -F -w -i icon.ico your_script.py ``` 3. **PyQt5 程序打包示例** ```bash # 安装依赖 pip install PyQt5 -i https://pypi.tuna.tsinghua.edu.cn/simple # 打包命令(需包含Qt插件) pyinstaller -F -w --add-data "venv/Lib/site-packages/PyQt5/Qt5/plugins;PyQt5/Qt5/plugins" your_qt_app.py ``` 4. **打包后文件位置** 生成文件位于 `dist/` 目录,`your_script.exe` 可直接运行[^3][^4] --- #### 二、Nuitka 打包方法(推荐性能敏感项目) 1. **安装 Nuitka** ```bash pip install nuitka zstandard -i https://pypi.tuna.tsinghua.edu.cn/simple ``` 2. **基础打包命令** ```bash # 生成独立文件夹 nuitka --standalone --follow-imports your_script.py # 生成单个EXE文件 nuitka --standalone --onefile your_script.py # 优化窗口程序 nuitka --standalone --onefile --windows-disable-console --windows-icon=icon.ico your_script.py ``` 3. **PyQt5 程序打包** ```bash # 启用PyQt5插件 nuitka --standalone --onefile --enable-plugin=pyqt5 --windows-icon=app.ico your_qt_app.py ``` 4. **编译优化选项** ```bash # 启用并行编译(加速) nuitka --jobs=4 --lto=yes your_script.py # 移除断言语句 nuitka --remove-output your_script.py ``` --- ### 三、打包工具对比 | 特性 | PyInstaller | Nuitka | |---------------------|---------------------------------|---------------------------------| | **打包速度** | 快(秒级) | 慢(需编译C代码,分钟级) | | **执行性能** | 解释执行 | 编译为本地代码,性能提升30%+ [^2] | | **文件大小** | 较大(包含Python解释器) | 较小(仅必要依赖) | | **反编译难度** | 容易(字节码) | 困难(机器码) | | **复杂项目支持** | 较好 | 需手动处理C依赖 | --- ### 四、常见问题解决 1. **缺失依赖问题** - PyInstaller使用 `--hidden-import` 指定隐藏模块 ```bash pyinstaller -F --hidden-import=module_name your_script.py ``` - Nuitka使用 `--include-module` 包含模块 ```bash nuitka --include-module=module_name your_script.py ``` 2. **资源文件打包** 创建 `hook.py` 文件: ```python # PyInstaller钩子示例 from PyInstaller.utils.hooks import collect_data_files datas = collect_data_files('your_module') ``` 打包时添加 `--additional-hooks-dir=.` 3. **防杀毒软件误报** - 使用代码签名证书(需购买) - 添加白名单:`nuitka --disable-dll-dependency-cache` --- ### 五、最佳实践建议 1. **虚拟环境打包** ```bash python -m venv pack_env pack_env\Scripts\activate pip install -r requirements.txt ``` 2. **版本信息注入**(Windows) 创建 `version.txt`: ``` filevers=(1,0,0,0) prodvers=(1,0,0,0) ... ``` 打包命令添加: ```bash pyinstaller -F --version-file=version.txt your_script.py ``` 3. **减少体积技巧** ```bash # 使用UPX压缩(下载upx.exe放至PATH) pyinstaller -F --upx-dir=C:\upx your_script.py ``` > ⚠️ **注意事项** > 1. 路径中避免中文和空格 > 2. 复杂项目建议先用 `--onedir` 模式测试 > 3. 32位程序需在32位Python环境下打包
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值