android对PDF文件的操作(上传、预览、下载和转存相册)
一、上传PDF文件到服务器
前段时间有一个老项目需要添加对PDF的上传、下载和预览等操作,我这边完成之后整理发一下博客,因为项目较老,框架技术也不是先进技术。
上传文件主要使用了OkGo网络请求框架,如果是使用OKhttp的将网络请求代码和回调方法改成相应的即可,因为上传文件需要用到网络请求权限和文件读写权限,记得需要在AndroidManifest.xml中配置:
<!--用于访问网络--><uses-permission android:name=\"android.permission.INTERNET\"></uses-permission><!--用于写入缓存数据到扩展存储卡--><uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"></uses-permission><!--用于读取扩展存储卡文件--><uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"></uses-permission>
上传文件代码:
(1)点击按钮调用打开手机的文件选择器:
// 打开系统的文件选择器public void pickFile(View view) {int permission = ActivityCompat.checkSelfPermission(this,Manifest.permission.READ_EXTERNAL_STORAGE);//缺少什么权限就写什么权限if (permission != PackageManager.PERMISSION_GRANTED) {// We don\'t have permission so prompt the userActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,}, 0);}int permission2 = ActivityCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE);//缺少什么权限就写什么权限if (permission2 != PackageManager.PERMISSION_GRANTED) {// We don\'t have permission so prompt the userActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,}, 0);}else{Intent intent = new Intent(Intent.ACTION_GET_CONTENT);//intent.setType(\"image/*\");//选择图片//intent.setType(\"audio/*\"); //选择音频//intent.setType(\"video/*\"); //选择视频 (mp4 3gp 是android支持的视频格式)//intent.setType(\"video/*;image/*\");//同时选择视频和图片intent.setType(\"*/*\");//无类型限制intent.addCategory(Intent.CATEGORY_OPENABLE);startActivityForResult(intent, 1);}}
(2)回调方法获取文件的真实路径,根据android的版本不同,采用不同的方法
// 获取文件的真实路径String path;@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if (resultCode == Activity.RESULT_OK) {Uri uri = data.getData();if (\"file\".equalsIgnoreCase(uri.getScheme())) {//使用第三方应用打开path = uri.getPath();ShowDiglog(path);System.out.println(\"获得地址1\"+path);return;}if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {//4.4以后path = getPath(this, uri);System.out.println(\"获得地址2\"+path);ShowDiglog(path);} else {//4.4以下下系统调用方法path = getRealPathFromURI(uri);System.out.println(\"获得地址3\"+path);ShowDiglog(path);}}}public String getRealPathFromURI(Uri contentUri) {String res = null;String[] proj = {MediaStore.Images.Media.DATA};Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);if (null != cursor && cursor.moveToFirst()) {;int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);res = cursor.getString(column_index);cursor.close();}return res;}/*** 专为Android4.4设计的从Uri获取文件绝对路径,以前的方法已不好使*/@SuppressLint(\"NewApi\")public String getPath(final Context context, final Uri uri) {final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;// DocumentProviderif (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {// ExternalStorageProviderif (isExternalStorageDocument(uri)) {final String docId = DocumentsContract.getDocumentId(uri);final String[] split = docId.split(\":\");final String type = split[0];if (\"primary\".equalsIgnoreCase(type)) {return Environment.getExternalStorageDirectory() + \"/\" + split[1];}}// DownloadsProviderelse if (isDownloadsDocument(uri)) {final String id = DocumentsContract.getDocumentId(uri);final Uri contentUri = ContentUris.withAppendedId(Uri.parse(\"content://downloads/public_downloads\"), Long.valueOf(id));return getDataColumn(context, contentUri, null, null);}// MediaProviderelse if (isMediaDocument(uri)) {final String docId = DocumentsContract.getDocumentId(uri);final String[] split = docId.split(\":\");final String type = split[0];Uri contentUri = null;if (\"image\".equals(type)) {contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;} else if (\"video\".equals(type)) {contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;} else if (\"audio\".equals(type)) {contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;}final String selection = \"_id=?\";final String[] selectionArgs = new String[]{split[1]};return getDataColumn(context, contentUri, selection, selectionArgs);}}// MediaStore (and general)else if (\"content\".equalsIgnoreCase(uri.getScheme())) {return getDataColumn(context, uri, null, null);}// Fileelse if (\"file\".equalsIgnoreCase(uri.getScheme())) {return uri.getPath();}return null;}/*** Get the value of the data column for this Uri. This is useful for* MediaStore Uris, and other file-based ContentProviders.** @param context The context.* @param uri The Uri to query.* @param selection (Optional) Filter used in the query.* @param selectionArgs (Optional) Selection arguments used in the query.* @return The value of the _data column, which is typically a file path.*/public String getDataColumn(Context context, Uri uri, String selection,String[] selectionArgs) {Cursor cursor = null;final String column = \"_data\";final String[] projection = {column};try {cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,null);if (cursor != null && cursor.moveToFirst()) {final int column_index = cursor.getColumnIndexOrThrow(column);return cursor.getString(column_index);}} finally {if (cursor != null)cursor.close();}return null;}/*** @param uri The Uri to check.* @return Whether the Uri authority is ExternalStorageProvider.*/public boolean isExternalStorageDocument(Uri uri) {return \"com.android.externalstorage.documents\".equals(uri.getAuthority());}/*** @param uri The Uri to check.* @return Whether the Uri authority is DownloadsProvider.*/public boolean isDownloadsDocument(Uri uri) {return \"com.android.providers.downloads.documents\".equals(uri.getAuthority());}/*** @param uri The Uri to check.* @return Whether the Uri authority is MediaProvider.*/public boolean isMediaDocument(Uri uri) {return \"com.android.providers.media.documents\".equals(uri.getAuthority());}
(3)点击弹窗的上传按钮,调用网络接口,实现文件上传
public void ShowDiglog(final String path){MyAlertDialog myAlertDialog = new MyAlertDialog(this).builder().setTitle(\"上传简历?\").setMsg(path).setPositiveButton(\"上传\", new View.OnClickListener() {@Overridepublic void onClick(View v) {uploadFile(path);System.out.println(\"上传\");}}).setNegativeButton(\"取消\", new View.OnClickListener() {@Overridepublic void onClick(View v) {System.out.println(\"取消\");}});myAlertDialog.show();}public void uploadFile(String path){File file = new File(path);OkGo.<String>post(NetUrl.DNS + NetUrl.UploadCv).tag(this).params(\"file\", file).execute(new StringCallback() {@Overridepublic void onSuccess(Response<String> response) {try {JSONObject jsonObject = new JSONObject(response.body());System.out.println(\"输出结果\"+jsonObject);int infoCode = jsonObject.getInt(\"status\");if (infoCode == 0) {Tools.toast(IntroFileActivity.this, \"简历上传成功!\");introAdapter.clear();pageNumber = 1;initData(pageNumber);}} catch (JSONException e) {e.printStackTrace();}}});}
二、预览网络上的PDF文件
因为android原生无法直接预览PDF文件,所有简单的就是将PDF文件下载下来转成图片进行预览,但是这样子下载到用户的手机中对用户不友好。在考虑之后,决定抵赖第三方的依赖来实现,需要引入pdfviewpager组件,在gradle组件中添加以下代码:
(1)添加依赖:
//预览pdf文件compile(\'es.voghdev.pdfviewpager:library:1.0.3\') {exclude module: \'support-v4\'exclude group: \'com.android.support\'}
(2)界面设计(引入依赖的界面组件):
<?xml version=\"1.0\" encoding=\"utf-8\"?><LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"xmlns:ptr=\"http://schemas.android.com/apk/res-auto\"android:layout_width=\"match_parent\"android:layout_height=\"match_parent\"android:background=\"@color/theme_background\"android:orientation=\"vertical\"><!-- toolbar菜单 --><RelativeLayoutandroid:layout_width=\"match_parent\"android:layout_height=\"@dimen/tj_toolbar_height\"android:background=\"@color/tj_toolbar_background\"android:orientation=\"vertical\" ><ImageViewandroid:id=\"@+id/toolbar_back\"android:layout_width=\"50dp\"android:layout_height=\"match_parent\"android:background=\"@drawable/tj_click_selector\"android:contentDescription=\"@null\"android:scaleType=\"center\"android:src=\"@drawable/btn_navbar_back\" /><TextViewandroid:id=\"@+id/toolbar_title\"android:layout_width=\"wrap_content\"android:layout_height=\"wrap_content\"android:layout_centerInParent=\"true\"android:text=\"@string/activity_invoice_file_title\"android:textColor=\"@color/tj_toolbar_title\"android:textSize=\"@dimen/tj_toolbar_title\" /><TextViewandroid:id=\"@+id/textView_download\"android:layout_width=\"wrap_content\"android:layout_height=\"match_parent\"android:layout_alignParentEnd=\"true\"android:layout_alignParentRight=\"true\"android:paddingLeft=\"10dp\"android:paddingRight=\"10dp\"android:background=\"@drawable/tj_click_selector\"android:gravity=\"center\"android:text=\"@string/activity_invoice_file_download\"android:textColor=\"@color/tj_text_content_d\"android:textSize=\"16sp\" /><Viewandroid:layout_width=\"match_parent\"android:layout_height=\"1px\"android:layout_alignParentBottom=\"true\"android:background=\"@color/tj_device_line_2\" /></RelativeLayout><RelativeLayoutandroid:id=\"@+id/remote_pdf_root\"android:layout_width=\"match_parent\"android:layout_height=\"match_parent\"><es.voghdev.pdfviewpager.library.PDFViewPagerandroid:id=\"@+id/pdfViewPager\"android:layout_width=\"match_parent\"android:layout_height=\"match_parent\" /></RelativeLayout><ProgressBarandroid:id=\"@+id/pb_bar\"android:layout_width=\"wrap_content\"android:layout_height=\"wrap_content\"android:layout_centerInParent=\"true\"android:layout_gravity=\"center\" /></LinearLayout>
显示效果如图,实际手机中不会有PDF图标:
(3)Activity代码:
public class ActivityInvoiceFile extends AppCompatActivity implements DownloadFile.Listener,OnClickListener {private RelativeLayout pdf_root;private ProgressBar pb_bar;private TextView TextViewDownload;private RemotePDFViewPager remotePDFViewPager;private String pdfUrl = \"\";private String invoiceNo = \"\";private PDFPagerAdapter adapter;private Context mContext;private String destFileDir;private String destFileName;protected RequestCall mRequest;private ProgressDialog progressDialog;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_invoice_file);mContext = ActivityInvoiceFile.this;setupView();setupData();setDownloadListener();}private void setupView() {findViewById(R.id.toolbar_back).setOnClickListener(this);pdf_root = (RelativeLayout) findViewById(R.id.remote_pdf_root);pb_bar = (ProgressBar) findViewById(R.id.pb_bar);}protected void setupData() {Intent intent = getIntent();//pdfUrl 为文件地址,比如www.xxx.com/file/xxx.pdfpdfUrl = intent.getStringExtra(\"pdfUrl\");}/*设置监听*/protected void setDownloadListener() {final DownloadFile.Listener listener = this;remotePDFViewPager = new RemotePDFViewPager(this, pdfUrl, listener);remotePDFViewPager.setId(R.id.pdfViewPager);}/*加载成功调用*/@Overridepublic void onSuccess(String url, String destinationPath) {pb_bar.setVisibility(View.GONE);adapter = new PDFPagerAdapter(this, FileUtil.extractFileNameFromURL(url));remotePDFViewPager.setAdapter(adapter);updateLayout();}/*更新视图*/private void updateLayout() {pdf_root.removeAllViewsInLayout();pdf_root.addView(remotePDFViewPager, LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);}/*加载失败调用*/@Overridepublic void onFailure(Exception e) {pb_bar.setVisibility(View.GONE);Toast.makeText(mContext, \"文件加载失败\", Toast.LENGTH_SHORT).show();}}
三、PDF文件的下载和转存相册
其实下载文件倒是简单,就是简单的文件下载方式,这里附上一个文件下载工具类,对异常处理和回调进行封装。
(1)文件下载工具类:
/*** 文件下载工具类(单例模式)*/public class DownloadUtil {private static DownloadUtil downloadUtil;private final OkHttpClient okHttpClient;public static DownloadUtil get() {if (downloadUtil == null) {downloadUtil = new DownloadUtil();}return downloadUtil;}private DownloadUtil() {okHttpClient = new OkHttpClient();}/*** @param url 下载连接* @param destFileDir 下载的文件储存目录* @param destFileName 下载文件名称* @param listener 下载监听*/public void download(final String url, final String destFileDir, final String destFileName, final OnDownloadListener listener) {Request request = new Request.Builder().url(url).build();okHttpClient.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {// 下载失败监听回调listener.onDownloadFailed(e);}@Overridepublic void onResponse(Call call, Response response) throws IOException {InputStream is = null;byte[] buf = new byte[2048];int len = 0;FileOutputStream fos = null;// 储存下载文件的目录File dir = new File(destFileDir);if (!dir.exists()) {dir.mkdirs();}File file = new File(dir, destFileName);try {is = response.body().byteStream();long total = response.body().contentLength();fos = new FileOutputStream(file);long sum = 0;while ((len = is.read(buf)) != -1) {fos.write(buf, 0, len);sum += len;int progress = (int) (sum * 1.0f / total * 100);// 下载中更新进度条listener.onDownloading(progress);}fos.flush();// 下载完成listener.onDownloadSuccess(file);} catch (Exception e) {listener.onDownloadFailed(e);} finally {try {if (is != null)is.close();} catch (IOException e) {}try {if (fos != null)fos.close();} catch (IOException e) {}}}});}public interface OnDownloadListener {/*** @param file 下载成功后的文件*/void onDownloadSuccess(File file);/*** @param progress 下载进度*/void onDownloading(int progress);/*** @param e 下载异常信息*/void onDownloadFailed(Exception e);}}
(2)文件下载和转存相册
在文件下载之后,回调方法返回其真实路径,通过真实路径获取到PDF文件,并将其转成Bitmap,在进一步保存图片到手机中,最后将图片文件插入到系统图库,实现了转存相册功能。Activity完整代码如下:
public class ActivityInvoiceFile extends AppCompatActivity implements DownloadFile.Listener,OnClickListener {private RelativeLayout pdf_root;private ProgressBar pb_bar;private TextView TextViewDownload;private RemotePDFViewPager remotePDFViewPager;private String pdfUrl = \"\";private String invoiceNo = \"\";private PDFPagerAdapter adapter;private Context mContext;private String destFileDir;private String destFileName;protected RequestCall mRequest;private ProgressDialog progressDialog;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_invoice_file);mContext = ActivityInvoiceFile.this;setupView();setupData();setDownloadListener();}private void setupView() {findViewById(R.id.toolbar_back).setOnClickListener(this);pdf_root = (RelativeLayout) findViewById(R.id.remote_pdf_root);pb_bar = (ProgressBar) findViewById(R.id.pb_bar);TextViewDownload = (TextView)findViewById(R.id.textView_download);TextViewDownload.setOnClickListener(this);}protected void setupData() {Intent intent = getIntent();pdfUrl = intent.getStringExtra(\"pdfUrl\");invoiceNo = intent.getStringExtra(\"invoiceNo\");}/*设置监听*/protected void setDownloadListener() {final DownloadFile.Listener listener = this;remotePDFViewPager = new RemotePDFViewPager(this, pdfUrl, listener);remotePDFViewPager.setId(R.id.pdfViewPager);}/*加载成功调用*/@Overridepublic void onSuccess(String url, String destinationPath) {pb_bar.setVisibility(View.GONE);adapter = new PDFPagerAdapter(this, FileUtil.extractFileNameFromURL(url));remotePDFViewPager.setAdapter(adapter);updateLayout();}/*更新视图*/private void updateLayout() {pdf_root.removeAllViewsInLayout();pdf_root.addView(remotePDFViewPager, LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);}/*加载失败调用*/@Overridepublic void onFailure(Exception e) {pb_bar.setVisibility(View.GONE);Toast.makeText(mContext, \"文件加载失败\", Toast.LENGTH_SHORT).show();}@Overridepublic void onProgressUpdate(int progress, int total) {}@Overridepublic void onClick(View v) {// TODO Auto-generated method stubswitch (v.getId()) {case R.id.toolbar_back:finish();break;case R.id.textView_download:downFile(pdfUrl);break;default:break;}}/*** 文件下载** @param url*/public void downFile(String url) {progressDialog = new ProgressDialog(ActivityInvoiceFile.this);progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);progressDialog.setProgress(0);progressDialog.setMax(100);progressDialog.show();progressDialog.setCancelable(true);DownloadUtil.get().download(url, Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+\"Invoices\", \"Invoice\"+invoiceNo+\".pdf\", new DownloadUtil.OnDownloadListener() {@Overridepublic void onDownloadSuccess(File file) {if (progressDialog != null && progressDialog.isShowing()) {progressDialog.dismiss();}Looper.prepare();//增加部分AlertsavePhoto(\"已下载至手机内部存储设备根目录下Invoices文件夹中\",\"是否转存图片至相册?\",file);Looper.loop();//增加部分}@Overridepublic void onDownloading(int progress) {progressDialog.setProgress(progress);}@Overridepublic void onDownloadFailed(Exception e) {if (progressDialog != null && progressDialog.isShowing()) {progressDialog.dismiss();}Looper.prepare();//增加部分Toast.makeText(mContext, \"文件下载失败\", Toast.LENGTH_SHORT).show();Looper.loop();//增加部分}});}//转存图片对话框private void AlertsavePhoto(String title, String msg, final File file) {new Builder(mContext).setTitle(title).setMessage(msg).setNegativeButton(android.R.string.cancel, null).setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {dialog.dismiss();ArrayList<Bitmap> bitmap = pdfToBitmap(file);saveImageToGallery(mContext,bitmap);}}).create().show();}//PDF转成Bitmapprivate ArrayList<Bitmap> pdfToBitmap(File pdfFile) {ArrayList<Bitmap> bitmaps = new ArrayList<>();try {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {PdfRenderer renderer = new PdfRenderer(ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY));Bitmap bitmap;final int pageCount = renderer.getPageCount();Log.e(\"test_sign\", \"图片de 张数: \" +pageCount);for (int i = 0; i < pageCount; i++) {PdfRenderer.Page page = renderer.openPage(i);int width = getResources().getDisplayMetrics().densityDpi / 72 * page.getWidth();int height = getResources().getDisplayMetrics().densityDpi / 72 * page.getHeight();bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);//todo 以下三行处理图片存储到本地出现黑屏的问题,这个涉及到背景问题Canvas canvas = new Canvas(bitmap);canvas.drawColor(Color.WHITE);canvas.drawBitmap(bitmap, 0, 0, null);Rect r = new Rect(0, 0, width, height);page.render(bitmap, r, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);bitmaps.add(bitmap);// close the pagepage.close();}// close the rendererrenderer.close();}} catch (Exception ex) {ex.printStackTrace();}return bitmaps;}private void saveImageToGallery(Context context, ArrayList<Bitmap> bitmaps) {// 首先保存图片File appDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + \"InvoicePhoto\");for (int i = 0; i < bitmaps.size(); i++) {if (!appDir.exists()) {appDir.mkdir();}String fileName = System.currentTimeMillis() + \".jpg\";File file = new File(appDir, fileName);Log.e(\"test_sign\", \"图片全路径localFile = \" + appDir.getAbsolutePath() + fileName);FileOutputStream fos = null;try {fos = new FileOutputStream(file);bitmaps.get(i).compress(Bitmap.CompressFormat.JPEG, 100, fos);fos.flush();fos.close();} catch (FileNotFoundException e) {Toast.makeText(mContext, \"保存到相册失败!\", Toast.LENGTH_LONG).show();e.printStackTrace();} catch (IOException e) {Toast.makeText(mContext, \"保存到相册失败!\", Toast.LENGTH_LONG).show();e.printStackTrace();} finally {if (fos != null) {try {fos.close();//回收bitmaps.get(i).recycle();} catch (IOException e) {e.printStackTrace();}}}// 其次把文件插入到系统图库try {MediaStore.Images.Media.insertImage(context.getContentResolver(),file.getAbsolutePath(), fileName, null);} catch (FileNotFoundException e) {Toast.makeText(mContext, \"保存到相册失败!\", Toast.LENGTH_LONG).show();e.printStackTrace();}}Toast.makeText(mContext, \"已保存到手机相册!\", Toast.LENGTH_LONG).show();// 最后通知图库更新sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.fromFile(new File(appDir.getPath()))));}}