You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过URLConnection实现PDF缓存以提升加载速度?

Android PDF文件本地缓存实现方案

问题场景

使用HttpURLConnection加载远程PDF时速度缓慢,需要实现本地缓存机制,再次访问同一PDF时直接读取本地文件,无需重复下载。

核心实现思路

  • 本地文件缓存:根据PDF的URL生成唯一文件名,将下载后的文件保存到APP私有目录,下次访问优先读取本地缓存。
  • 服务器缓存校验:通过If-Modified-Since请求头询问服务器文件是否更新,避免下载未修改的文件。
  • 优化IO操作:使用缓冲流提升读写效率,避免内存溢出。

修改后的完整代码

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.AsyncTask;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.github.barteksc.pdfviewer.PDFView;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

public class YourPDFActivity extends Activity {
    private ListView myPDFListView;
    private PDFView pdfView;
    private DatabaseReference databaseReference;
    private List<uploadPDF> uploadPDFS = new ArrayList<>();

    @Override
    protected void onCreate(@Nullable android.os.Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.your_main_layout);
        myPDFListView = findViewById(R.id.your_list_view_id);
        viewAllFiles();

        myPDFListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                setContentView(R.layout.viewfile_activity);
                if(getResources().getBoolean(R.bool.portrait_only)){
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                } else {
                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
                }
                pdfView = findViewById(R.id.pdfView);
                uploadPDF targetPDF = uploadPDFS.get(position);
                new RetrievePDFStream().execute(targetPDF.url);
            }
        });
    }

    class RetrievePDFStream extends AsyncTask<String, Void, InputStream> {
        private File cacheFile;

        @Override
        protected InputStream doInBackground(String... strings) {
            String pdfUrl = strings[0];
            // 提前初始化Cookie管理
            CookieManager cookieManager = new CookieManager();
            CookieHandler.setDefault(cookieManager);

            // 1. 获取本地缓存文件
            cacheFile = getCacheFile(pdfUrl);
            if (cacheFile.exists()) {
                try {
                    // 校验服务器文件是否未更新
                    if (isFileUnchangedOnServer(pdfUrl, cacheFile.lastModified())) {
                        return new FileInputStream(cacheFile);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            // 2. 无有效缓存则重新下载
            InputStream inputStream = null;
            HttpURLConnection urlConnection = null;
            try {
                URL urlx = new URL(pdfUrl);
                urlConnection = (HttpURLConnection) urlx.openConnection();
                // 添加缓存校验请求头
                if (cacheFile.exists()) {
                    urlConnection.setRequestProperty("If-Modified-Since", formatLastModifiedTime(cacheFile.lastModified()));
                }

                int responseCode = urlConnection.getResponseCode();
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    inputStream = new BufferedInputStream(urlConnection.getInputStream());
                    // 将下载内容写入缓存
                    saveStreamToCache(inputStream, cacheFile);
                    // 返回缓存文件的输入流
                    return new FileInputStream(cacheFile);
                } else if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                    // 文件未修改,直接用本地缓存
                    return new FileInputStream(cacheFile);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 确保资源释放
                if (urlConnection != null) {
                    urlConnection.disconnect();
                }
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }

        @Override
        protected void onPostExecute(InputStream inputStream) {
            if (inputStream != null) {
                pdfView.fromStream(inputStream)
                        .enableSwipe(true)
                        .load();
                // 关闭流避免资源泄漏
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        // 根据URL生成唯一缓存文件名
        private File getCacheFile(String url) {
            String fileName = generateMD5(url) + ".pdf";
            return new File(getCacheDir(), fileName);
        }

        // MD5加密URL生成唯一标识
        private String generateMD5(String input) {
            try {
                MessageDigest md = MessageDigest.getInstance("MD5");
                byte[] digest = md.digest(input.getBytes());
                StringBuilder hexBuilder = new StringBuilder();
                for (byte b : digest) {
                    String hex = Integer.toHexString(0xFF & b);
                    if (hex.length() == 1) {
                        hexBuilder.append('0');
                    }
                    hexBuilder.append(hex);
                }
                return hexBuilder.toString();
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
        }

        // 校验服务器文件是否未更新
        private boolean isFileUnchangedOnServer(String url, long localLastModified) throws IOException {
            HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
            connection.setRequestMethod("HEAD");
            connection.setRequestProperty("If-Modified-Since", formatLastModifiedTime(localLastModified));
            int responseCode = connection.getResponseCode();
            connection.disconnect();
            return responseCode == HttpURLConnection.HTTP_NOT_MODIFIED;
        }

        // 格式化时间为HTTP标准格式
        private String formatLastModifiedTime(long timestamp) {
            if (timestamp <= 0) return "";
            SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
            sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
            return sdf.format(new Date(timestamp));
        }

        // 将输入流写入缓存文件
        private void saveStreamToCache(InputStream inputStream, File cacheFile) throws IOException {
            BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(cacheFile));
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            outputStream.flush();
            outputStream.close();
        }
    }

    private void viewAllFiles() {
        databaseReference = FirebaseDatabase.getInstance().getReference("uploads");
        databaseReference.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                uploadPDFS.clear(); // 避免重复加载数据
                for(DataSnapshot postSnapshot : snapshot.getChildren()){
                    uploadPDF uploadPDF = postSnapshot.getValue(com.tokha.diplom.uploadPDF.class);
                    uploadPDFS.add(uploadPDF);
                }
                String[] uploads = new String[uploadPDFS.size()];
                for(int i = 0;i<uploads.length;i++){
                    uploads[i] = uploadPDFS.get(i).getName();
                }
                ArrayAdapter<String> adapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1, uploads){
                    @NonNull
                    @Override
                    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
                        View view = super.getView(position, convertView, parent);
                        TextView myText = view.findViewById(android.R.id.text1);
                        myText.setTextColor(getResources().getColor(R.color.autorization));
                        return view;
                    }
                };
                myPDFListView.setAdapter(adapter);
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {
                // 可添加错误提示逻辑
            }
        });
    }

    // 假设的uploadPDF实体类
    public static class uploadPDF {
        public String url;
        public String name;

        public uploadPDF() {}

        public uploadPDF(String url, String name) {
            this.url = url;
            this.name = name;
        }
    }
}

关键优化点说明

  • 缓存文件管理:通过MD5加密URL生成唯一文件名,避免缓存冲突;使用APP私有缓存目录getCacheDir(),系统会在存储空间不足时自动清理。
  • 服务器校验:发送HEAD请求和If-Modified-Since头,仅在文件更新时才重新下载,节省带宽。
  • IO优化:使用8KB缓冲流提升读写速度,减少IO操作次数。
  • 资源安全:在finally块中强制关闭流和网络连接,避免资源泄漏。

额外建议

  • 替换AsyncTask:AsyncTask已被标记为过时,建议改用Kotlin协程或ExecutorService实现异步操作,代码更简洁且生命周期更安全。
  • 缓存清理:可添加手动清理缓存的功能,或定期清理超过7天的过期缓存文件。

内容的提问来源于stack exchange,提问作者SmyhkS Channe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 09:31:02