目录:
- 功能需求--分析
- 具体实现--动手
- 源码分析--优化
- 造轮子--demo
- 总结
1. 功能需求--分析
文字描述:点击 Button 下载 pdf 文件,并在应用内显示。
具体展示:
2019-3-6
思考实现方式:
[1] 文件下载--AsyncHttpClient
;
[2] 应用内打开 pdf 文件 -- pdfviewpager
;
尝试过 android-pdf-viewer
开源库,对 assets 目录下的文件打开很友好,但是对于下载后显示 pdf 文件有时会闪退。
也尝试过 TBS SDK
, 对于异常情况显示出一片空白,最终用了pdfviewpager
,终于可以了。
2. 具体实现--动手
2.1 add compile in your build.gradle
compile 'es.voghdev.pdfviewpager:library:1.0.6'
2.2 use it in your Activity
[ 1、Button 点击]
case R.id.btn_download:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Intent intent = new Intent(FlightCertificateInfoActivity.this, WatchPDFActivity.class);
intent.putExtra("etno", etNo);
startActivity(intent);
} else {
loadPdf(etNo);
}
break;
[2、WatchPDFActivity.class]
import android.annotation.TargetApi;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.pdf.PdfRenderer;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.jiuair.booking.R;
import com.jiuair.booking.tools.AsyncHttpUtils;
import com.jiuair.booking.tools.ViewTool;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.RequestParams;
import org.apache.http.Header;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class WatchPDFActivity extends FragmentActivity implements View.OnClickListener {
// private PDFView pdfView;
private ImageView mImageView;
private ParcelFileDescriptor mFileDescriptor;
private PdfRenderer mPdfRenderer;
private PdfRenderer.Page mCurrentPage;
private Button mButtonPrevious;
private Button mButtonNext;
private LinearLayout ll_1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_watch_pdf);
mImageView = (ImageView) findViewById(R.id.image);
ll_1 = (LinearLayout) findViewById(R.id.ll_1);
// pdfView = (PDFView) findViewById(R.id.pdfView);
mButtonPrevious = (Button) findViewById(R.id.previous);
mButtonNext = (Button) findViewById(R.id.next);
// Bind events.
mButtonPrevious.setOnClickListener(this);
mButtonNext.setOnClickListener(this);
String id = getIntent().getStringExtra("id");
String abpno = getIntent().getStringExtra("abpno");
loadPdf(id, abpno);
}
/**
* @param etno
* @param abpno 下载并打开pdf文件
*/
private void loadPdf(String id, String abpno) {
String url = "/downloadFile";
RequestParams params = new RequestParams();
params.put("id", id);
final ProgressDialog layerMask = ViewTool.showLayerMask(WatchPDFActivity.this);
AsyncHttpUtils.get(url, params, new AsyncHttpResponseHandler() {
@Override
public void onStart() {
// called before request is started
Log.e("ATG", "开始获取pdf》》");
}
@Override
public void onSuccess(int statusCode, Header[] headers,
byte[] response) {
layerMask.dismiss();
comLoad(response);
}
@Override
public void onFailure(int statusCode, Header[] headers,
byte[] errorResponse, Throwable e) {
e.printStackTrace();
Toast.makeText(WatchPDFActivity.this, "PDF打开失败", Toast.LENGTH_SHORT).show();
WatchPDFActivity.this.finish();
//called when response HTTP status is "4XX" (eg. 401, 403, 404)
}
@Override
public void onRetry(int retryNo) {
// called when request is retried
}
});
}
/**
* @param response 普通的下载pdf到本地然后再打开pdf
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void comLoad(byte[] response) {
BufferedOutputStream bos = null;
FileOutputStream fos = null;
File file = null;
try {
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
String fileName = Environment.getExternalStorageDirectory() + "/cxy/" + "resume.pdf";
file = new File(fileName);
// 目录不存在创建目录
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos);
bos.write(response);
mFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
// This is the PdfRenderer we use to render the PDF.
if (mFileDescriptor != null) {
mPdfRenderer = new PdfRenderer(mFileDescriptor);
}
showPage(0);
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(WatchPDFActivity.this, "PDF打开失败", Toast.LENGTH_SHORT).show();
WatchPDFActivity.this.finish();
} finally {
try {
if (bos != null) {
bos.close();
}
if (fos != null) {
fos.close();
}
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onDestroy() {
super.onDestroy();
try {
closeRenderer();
} catch (IOException e) {
e.printStackTrace();
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void closeRenderer() throws IOException {
if (null != mCurrentPage) {
mCurrentPage.close();
}
if (null != mPdfRenderer)
mPdfRenderer.close();
if (null != mFileDescriptor)
mFileDescriptor.close();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void showPage(int index) {
if (mPdfRenderer.getPageCount() <= index) {
return;
}
// Make sure to close the current page before opening another one.
if (null != mCurrentPage) {
mCurrentPage.close();
}
// Use `openPage` to open a specific page in PDF.
mCurrentPage = mPdfRenderer.openPage(index);
// Important: the destination bitmap must be ARGB (not RGB).
Bitmap bitmap = Bitmap.createBitmap(mCurrentPage.getWidth(), mCurrentPage.getHeight(),
Bitmap.Config.ARGB_8888);
// Here, we render the page onto the Bitmap.
// To render a portion of the page, use the second and third parameter. Pass nulls to get
// the default result.
// Pass either RENDER_MODE_FOR_DISPLAY or RENDER_MODE_FOR_PRINT for the last parameter.
mCurrentPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
// We are ready to show the Bitmap to user.
mImageView.setImageBitmap(bitmap);
updateUi();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void updateUi() {
int index = mCurrentPage.getIndex();
int pageCount = mPdfRenderer.getPageCount();
mButtonPrevious.setEnabled(0 != index);
mButtonNext.setEnabled(index + 1 < pageCount);
if (pageCount <= 1) {
ll_1.setVisibility(View.GONE);
} else {
ll_1.setVisibility(View.VISIBLE);
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.previous: {
// Move to the previous page
showPage(mCurrentPage.getIndex() - 1);
break;
}
case R.id.next: {
// Move to the next page
showPage(mCurrentPage.getIndex() + 1);
break;
}
}
}
}
3. 框架分析--优化
3.1 android-async-http
需求中的 pdf 文件一般是一页,大小为 23 kb,属于小文件了,而且整个项目使用的是 android-async-http 框架,所以就用了。但如果是大文件,考虑到网络问题,可能有需要暂停、或者有 wifi 了再下载的情况,那么就应该用 HttpURLConnection 实现断点续传,AsyncTask<String, Integer, Boolean>异步管理下载文件 。
3.2 pdfviewpager
如果需求没有指定一定要在应用内打开,更优化的操作步骤是 [1] 判断用户手机是否有安装 wps 之类的文件查看器,[2] 如果有则调用 WPS 等应用打开,反之则按上述步骤,先判断文件是否存在,[3] 存在则直接打开,反之下载后再打开。
4. 造轮子--demo
5. 总结
最近看了一本书《不敢止步(一个软件工匠的12年)》,作者大四那份工作的历程的感悟让我产生巨大的共鸣,从毕业到现在有快1年了,事情经历得多了就会有迷惑,掉坑里多了就会去反思,愿不负 cxy
这个名字!
文章是 Android 面向需求开发系列中的一文,更多相关文章请关注。如若有什么问题,也可以通过扫描二维码发消息给我。转载请注明出处,谢谢!
二维码作者:Emily CH
2019年3月7日
网友评论