Android WebView on the mobile phone gets the blob link file name and downloads the pdf file dynamically generated by the web page and calls an external program to open it

Table of contents

1. There is already a practical dynamic pdf generation scheme,

2. Create a Layout in the Android editor and add a WebView:

3. extends Activity assigns the above WebView to a variable (instantiation?), and sets a large number of its properties to extend its functions:

4. Introduce the method of reference 1 and define the download completion listener

5. Obtain the blob link file name and type

6. Rewrite the download listener DownloadListener of WebView 

7. Several variables and other explanations


References:

1. Android WebView supports downloading blob protocol files_Misdirection_XG's Blog-CSDN Blog_android blob

2.  Android Webview implements file download function - huidaoli - 博客园

3. Convert between base64 and Blob_weixin_30776863's Blog-CSDN Blog

4. Android: The way you want to interact with WebView and JS is here - Baidu Library

Applied to: The most reasonable way to download html to pdf files supports Chinese_jessezappy's Blog-CSDN Blog

Project requirements:

1. Web pages dynamically generate pdf files.

2. Open the above webpage on the mobile phone to download the pdf file, and open the pdf file with an external program.

Previous article: The most reasonable way to download html to pdf files supports Chinese_jessezappy's blog-CSDN blog  , which has realized the dynamic generation of pdf files from web pages, and can automatically download dynamically generated pdf files on the PC side in many ways files such as:

pdf.save("A4.pdf") ;

But the original plan of my project is to open this webpage with a mobile phone to download the pdf. When those methods arrive at the mobile terminal, after opening the webpage with WebView, the pdf document generated by the webpage js cannot be downloaded.

After in-depth research on jspdf.umd.js, it is found that there are many parameters for the output pdf data:

    /**
     * Generates the PDF document.
     *
     * If `type` argument is undefined, output is raw body of resulting PDF returned as a string.
     *
     * @param {string} type A string identifying one of the possible output types.<br/>
     *                      Possible values are: <br/>
     *                          'arraybuffer' -> (ArrayBuffer)<br/>
     *                          'blob' -> (Blob)<br/>
     *                          'bloburi'/'bloburl' -> (string)<br/>
     *                          'datauristring'/'dataurlstring' -> (string)<br/>
     *                          'datauri'/'dataurl' -> (undefined) -> change location to generated datauristring/dataurlstring<br/>
     *                          'dataurlnewwindow' -> (window | null | undefined) throws error if global isn't a window object(node)<br/>
     *                          'pdfobjectnewwindow' -> (window | null) throws error if global isn't a window object(node)<br/>
     *                          'pdfjsnewwindow' -> (wind | null)
     * @param {Object|string} options An object providing some additional signalling to PDF generator.<br/>
     *                                Possible options are 'filename'.<br/>
     *                                A string can be passed instead of {filename:string} and defaults to 'generated.pdf'
     * @function
     * @instance
     * @returns {string|window|ArrayBuffer|Blob|jsPDF|null|undefined}
     * @memberof jsPDF#
     * @name output
     */


    var output = API.output = API.__private__.output = SAFE(function output(type, options) {
      options = options || {};

      if (typeof options === "string") {
        options = {
          filename: options
        };
      } else {
        options.filename = options.filename || "generated.pdf";
      }

      switch (type) {
        case undefined:
          return buildDocument();

        case "save":
          API.save(options.filename);
          break;

        case "arraybuffer":
          return getArrayBuffer(buildDocument());

        case "blob":
          return getBlob(buildDocument());

        case "bloburi":
        case "bloburl":
          // Developer is responsible of calling revokeObjectURL
          if (typeof globalObject.URL !== "undefined" && typeof globalObject.URL.createObjectURL === "function") {
            return globalObject.URL && globalObject.URL.createObjectURL(getBlob(buildDocument())) || void 0;
          } else {
            console.warn("bloburl is not supported by your system, because URL.createObjectURL is not supported by your browser.");
          }

          break;

        case "datauristring":
        case "dataurlstring":
          var dataURI = "";
          var pdfDocument = buildDocument();

          try {
            dataURI = btoa(pdfDocument);
          } catch (e) {
            dataURI = btoa(unescape(encodeURIComponent(pdfDocument)));
          }

          return "data:application/pdf;filename=" + options.filename + ";base64," + dataURI;

        case "pdfobjectnewwindow":
          if (Object.prototype.toString.call(globalObject) === "[object Window]") {
            var pdfObjectUrl = "https://cdnjs.cloudflare.com/ajax/libs/pdfobject/2.1.1/pdfobject.min.js";
            var integrity = ' integrity="sha512-4ze/a9/4jqu+tX9dfOqJYSvyYd5M6qum/3HpCLr+/Jqf0whc37VUbkpNGHR7/8pSnCFw47T1fmIpwBV7UySh3g==" crossorigin="anonymous"';

            if (options.pdfObjectUrl) {
              pdfObjectUrl = options.pdfObjectUrl;
              integrity = "";
            }

            var htmlForNewWindow = "<html>" + '<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;}  </style><body><script src="' + pdfObjectUrl + '"' + integrity + '></script><script >PDFObject.embed("' + this.output("dataurlstring") + '", ' + JSON.stringify(options) + ");</script></body></html>";
            var nW = globalObject.open();

            if (nW !== null) {
              nW.document.write(htmlForNewWindow);
            }

            return nW;
          } else {
            throw new Error("The option pdfobjectnewwindow just works in a browser-environment.");
          }

        case "pdfjsnewwindow":
          if (Object.prototype.toString.call(globalObject) === "[object Window]") {
            var pdfJsUrl = options.pdfJsUrl || "./examples/PDF.js/web/viewer.html";
            var htmlForPDFjsNewWindow = "<html>" + "<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;}  </style>" + '<body><iframe id="pdfViewer" src="' + pdfJsUrl + "?file=&downloadName=" + options.filename + '" width="500px" height="400px" />' + "</body></html>";
            var PDFjsNewWindow = globalObject.open();
			console.log(htmlForPDFjsNewWindow);
            if (PDFjsNewWindow !== null) {
              PDFjsNewWindow.document.write(htmlForPDFjsNewWindow);
              var scope = this;

              PDFjsNewWindow.document.documentElement.querySelector("#pdfViewer").onload = function () {
                PDFjsNewWindow.document.title = options.filename;
                PDFjsNewWindow.document.documentElement.querySelector("#pdfViewer").contentWindow.PDFViewerApplication.open(scope.output("bloburl"));
              };
            }

            return PDFjsNewWindow;
          } else {
            throw new Error("The option pdfjsnewwindow just works in a browser-environment.");
          }

        case "dataurlnewwindow":
          if (Object.prototype.toString.call(globalObject) === "[object Window]") {
            var htmlForDataURLNewWindow = "<html>" + "<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;}  </style>" + "<body>" + '<iframe src="' + this.output("datauristring", options) + '"></iframe>' + "</body></html>";
			
            var dataURLNewWindow = globalObject.open();

            if (dataURLNewWindow !== null) {
              dataURLNewWindow.document.write(htmlForDataURLNewWindow);
              dataURLNewWindow.document.title = options.filename;
            }

            if (dataURLNewWindow || typeof safari === "undefined") return dataURLNewWindow;
          } else {
            throw new Error("The option dataurlnewwindow just works in a browser-environment.");
          }

          break;

        case "datauri":
        case "dataurl":
          return globalObject.document.location.href = this.output("datauristring", options);

        default:
          return null;
      }
    });

The above parameters are used when calling pdf.Output in CallBack:

var link = document.getElementById('linklink');
link.target = '_blank';
//link.href = window.URL.createObjectURL(convertBase64UrlToBlob(pdf.output('datauristring',{filename: 'A4.pdf'})));//140ms Base64 数据转 Blob
//link.href = window.URL.createObjectURL(pdf.output('blob',{filename: 'A4.pdf'}));//77ms Base64 数据
link.href = pdf.output('bloburi');//77ms 直接输出Blob 链接
//link.href = 
//pdf.output('pdfobjectnewwindow',{filename: 'A41.pdf'});//弹出对象窗口,无用
//link.href = pdf.output('dataurl',{filename: 'A4.pdf'});//数据链接无用				
//pdf.output('pdfjsnewwindow',{filename: 'A42.pdf'});//弹出窗口
//pdf.output('dataurlnewwindow',{filename: 'A43.pdf'});//弹出窗口,无用
link.download ="A41.pdf";				
link.text='点击这里下载';

After comparison, it is found that without considering the generation of pdf files in the background, only the blob link generated is suitable for mobile phone foreground downloads, but only Huawei's own browser supports downloading blob links and the webpage must be opened with Huawei browser. QQ, Baidu, etc. do not support downloading blob links.

The iframe.src = pdf.output('datauristring'); method is used in the original example of jspdf, and the returned pdf data encoded by Base64 can be converted to a Blob link using convertBase64UrlToBlob in Reference 3. After analyzing the Out method of jspdf It is found that it can directly output the Blob link, so the method in reference 3 will not be used for the time being.

It is finally decided that the webpage JS will generate a blob link to the A tag, click the A tag on the mobile phone to download it as a pdf document, and open it.

(I just wrote a long paragraph, and after pressing Ctrl+z, it’s all gone, and the draft is gone, do you want me to reorganize it???)

Then, let's reorganize and sort out the solutions, and I won't write the fragmentary analysis.

So, the project solution steps start:

1. There is already a practical dynamic pdf generation scheme, see:

The most reasonable way to download html to pdf files supports Chinese - jessezappy's blog - CSDN blog

2. Create a Layout in the Android editor and add a WebView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/webfrm"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <WebView
        android:id="@+id/webshow"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        android:layout_alignParentLeft="true" />
    
</RelativeLayout>

3. extends Activity assigns the above WebView to a variable (instantiation?), and sets a large number of its properties to extend its functions:

WebView mWebView;

Assign mWebView in onCreate:

mWebView=(WebView)findViewById(R.id.webshow);
setWebStyle();

Set mWebView properties and rewrite some of its actions, extended functions:

@SuppressLint("SetJavaScriptEnabled")
	private void setWebStyle() {  
    	WebSettings  webseting  =  mWebView.getSettings();  
        
        
        webseting.setAppCachePath(getApplicationContext().getCacheDir().getAbsolutePath());
        webseting.setUseWideViewPort(true);
        webseting.setLoadWithOverviewMode(true);
        //webseting.setPluginState(WebSettings.PluginState.ON);        
        webseting.setDomStorageEnabled(true);//最重要的方法,一定要设置,这就是出不来的主要原因  //webseting.setDomStorageEnabled(true);
        
        webseting.setSupportZoom(true);  
        webseting.setDefaultTextEncodingName("utf-8");
        /* 下载blob准备 */        
		webseting.setJavaScriptEnabled(true);
		webseting.setJavaScriptCanOpenWindowsAutomatically(true);

        /***********************/
        mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);// 去掉底部和右边的滚动条
        mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);  // 去掉底部和右边的滚动条
        
        mWebView.requestFocus();  
      
         webseting.setCacheMode(WebSettings.LOAD_DEFAULT);   // 默认使用缓存 
        // webseting.setCacheMode(WebSettings.LOAD_NO_CACHE); //默认不使用缓存!
         webseting.setAppCacheMaxSize(1024*1024*20);//设置缓冲大小,便于第一次缓存字体,否则每次下载字体需时太长。 
         String  appCacheDir=this.getApplicationContext().getDir("cache",Context.MODE_PRIVATE).getPath();       
                 webseting.setAppCachePath(appCacheDir);   
                 webseting.setAllowFileAccess(true);   
                 webseting.setAppCacheEnabled(true);   
                 webseting.setCacheMode(WebSettings.LOAD_DEFAULT|WebSettings.LOAD_CACHE_ELSE_NETWORK);
          
      //webview 动作重写 
        mWebView.setWebViewClient(new WebViewClient(){
            @Override
			public boolean shouldOverrideUrlLoading(WebView view, String url) {      
                view.loadUrl(url);   
                return false;// false 显示frameset, true 不显示Frameset ,内嵌页面
                //return true;      
            }     
            @Override    
            public void onPageStarted(WebView view, String url, Bitmap favicon) {    
                //有页面跳转时被回调             	
            	//dialog = ProgressDialog.show(webxj.this,null,"数据加载中,请稍侯...");              	
            	super.onPageStarted(view, url,favicon);  
            }              
            @Override
			public void onPageFinished(WebView view, String url) {   
            	//页面跳转结束后被回调  
            	//dialog.dismiss();
                super.onPageFinished(view, url);   
            }   
            @Override    
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {    
            	 super.onReceivedError(view, errorCode, description, failingUrl);//错误处理+ description
                Toast.makeText(webxj.this, "错误:请检查网络链接! " , Toast.LENGTH_SHORT).show();  
                dialog.dismiss(); 
		         new Handler().postDelayed(new Runnable() {  
			            public void run() {  
			            	mWebView.loadUrl(urlw);    //显示等待画面
			            }        
			        }, 500);                 
            }
            
        });  
        //禁用webview右键功能:长按
        mWebView.setOnLongClickListener(new OnLongClickListener(){
        	public boolean onLongClick(View v) {
				// TODO Auto-generated method stub
				return true;
			}
        });

        //blob
        //下载支持,A 标签内需 download
        mWebView.setDownloadListener(new MyWebViewDownLoadListener());    //  设置 WebView 下载监听器  
        mWebView.addJavascriptInterface(mDownloadBlobFileJSInterface, "Android"); // Blob 下载 js 定义
		mDownloadBlobFileJSInterface.setDownloadGifSuccessListener(new openDownloadfile()); // Blob 下载完成后监听器
     } 

4. Introduce the method of reference 1 and define the download completion listener

Note: The download completion listener has been set correctly in step 3 above:

mDownloadBlobFileJSInterface.setDownloadGifSuccessListener(new openDownloadfile()); //下载完成后监听器

From Reference 1:

mDownloadBlobFileJSInterface.setDownloadGifSuccessListener(absolutePath -> Toast.makeText(MainActivity.this,"下载成功,在Download目录下",Toast.LENGTH_SHORT).show());

There is a problem with this sentence. AbsolutePath is not defined. I suspect that it is a copied code. It is not copied completely. After analysis, it is found that it is the definition of a download completion listener. Then, according to the definition of rewriting DownloadListener, define a listener for downloading. Processing after completion, such as calling an external program to open:

/* 定义下载完成事件监听器 */
private class openDownloadfile implements DownloadGifSuccessListener {    	
	public void ondownloadGifSuccess(String gifFile){
		String fileName=gifFile.substring(gifFile.lastIndexOf("/")+1); 
		String directory=gifFile.substring(0,gifFile.lastIndexOf("/")+1);
		// System.out.println("11.下载成功,fileName:"+fileName);
		// System.out.println("12.下载成功,directory:"+directory);
		
        File File = new File(directory,fileName);		
		Intent intent = getFileIntent(File); 
        startActivity(intent); //调用万部程序打开
		// System.out.println("13.下载成功,在Download目录下:"+gifFile);
	}  
}

public Intent getFileIntent(File file){  
//       Uri uri = Uri.parse("http://m.ql18.com.cn/hpf10/1.pdf");  
        Uri uri = Uri.fromFile(file);  
        String type = getMIMEType(file);  
        // Log.e("tag", "type="+type);  
        Intent intent = new Intent("android.intent.action.VIEW");  
        intent.addCategory("android.intent.category.DEFAULT");  
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
        intent.setDataAndType(uri, type);  
        return intent;  
      } 

private String getMIMEType(File f){     
      String type="";    
      String fName=f.getName();    
      // * 取得扩展名 * /    
      String end=fName.substring(fName.lastIndexOf(".")+1,fName.length());//.toLowerCase();
      Locale loc = Locale.getDefault();//可以去掉这步
      end=end.toLowerCase(loc);
      // * 依扩展名的类型决定MimeType * /  
      if(end.equals("pdf")){  
          type = "application/pdf";//  
      }  
      else if(end.equals("m4a")||end.equals("mp3")||end.equals("mid")||    
      end.equals("xmf")||end.equals("ogg")||end.equals("wav")){    
        type = "audio/*";     
      }    
      else if(end.equals("3gp")||end.equals("mp4")){    
        type = "video/*";    
      }    
      else if(end.equals("jpg")||end.equals("gif")||end.equals("png")||    
      end.equals("jpeg")||end.equals("bmp")){    
        type = "image/*";    
      }    
      else if(end.equals("apk")){     
        // * android.permission.INSTALL_PACKAGES * /     
        type = "application/vnd.android.package-archive";   
      }  
//      else if(end.equals("pptx")||end.equals("ppt")){  
//        type = "application/vnd.ms-powerpoint";   
//      }else if(end.equals("docx")||end.equals("doc")){  
//        type = "application/vnd.ms-word";  
//      }else if(end.equals("xlsx")||end.equals("xls")){  
//        type = "application/vnd.ms-excel";  
//      }  
      else{  
//        // * 如果无法直接打开,就跳出软件列表给用户选择 * /    
        type="*/*"; //因注释多加了一个空格 
      }  
      return type;  
    }  

where getFileIntent and getMIMEType are from reference 2.

The DownloadBlobFileJSInterface in Reference 1 can be used directly, and the conversion to Base64 and the saving process need to be modified when the Blob file name is obtained later.

5. Obtain the blob link file name and type

In Reference 1, it is impossible to obtain the Blob file name and type, so a workaround is required: call the getDname function in the JS preset on the web page to obtain the file name, which has been previously determined by the callback function of the .html method medium preset.

<script>	
    // 网页中已由 link.download ="A41.pdf"; 设置文件名
pdf.html(document.getElementById('pdfx'), {	// 只有 addFileToVFS 方法添加的字体才能用于 .html 方法
			callback: function (pdf) {									
				d = new Date();timex=d.getTime();
				var link = document.getElementById('linklink');
				link.target = '_blank';				
				link.href = pdf.output('bloburi');//77ms
				link.download ="A41.pdf";				
				link.text='点击这里下载';
				d = new Date();	
				timex=d.getTime()-timex;
				console.log('总用时'+timex + "ms");
				//link.click();
				//pdf.save("A4.pdf");//自动下载
				//getDname(link.href);
			}
		});

    // 用于 安卓 WebView 调用获取 download 属性的文件名
        function getDname(s){			
			var a=document.getElementsByTagName("a");
			var h="";
			for(let i=0;i<a.length;i++){
				console.log(a[i].href);
				if(a[i].href===s){				
					h=a[i].download;
					i=a.length;
				}
			}
			console.log(h);
			return h;
		}
</script>

6. Rewrite the download listener DownloadListener of WebView 

Note: The download listener is set in setWebStyle();:

mWebView.setDownloadListener(new MyWebViewDownLoadListener());

In the download listener, judge whether it is a Blob link, if not, start the direct download process in Reference 2.

If yes, enable the Blob download process in Reference 1, and call the getDname js function in the web page to obtain the preset Blob file name before downloading:

    // 改装blob下载 
  private class MyWebViewDownLoadListener implements DownloadListener {    	  
		public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype,long contentLength){  
			if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
				Toast.makeText(webxj.this, "需要SD卡。",  Toast.LENGTH_SHORT).show();
				return;  
			}			
			//Toast.makeText(webxj.this,"Url:"+url.lastIndexOf("data:app"), Toast.LENGTH_SHORT).show();
			// System.out.println("Url:"+url);
			// Log.e("tag", "Url="+url);			
			if(url.indexOf("blob:http")==0){
				urlP=url;
				// System.out.println("打开:"+urlP);
				//mWebView.loadUrl("javascript:calljs();");				
				mWebView.evaluateJavascript("javascript:getDname('"+urlP+"')", new ValueCallback<String>(){
					public void onReceiveValue(String value){						
						// System.out.println("JS返回::"+value);
						pdffn=value.replace("\"","");
						// System.out.println("pdffn:"+pdffn);
						File directory= Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);  
						File file=new File(directory,pdffn);  
						if(file.exists()){  
							// Log.e("tag", "The file has already exists.");  //文件已存在
							System.out.println("文件已存在:"+directory+pdffn);
							Toast.makeText(webxj.this, "文件已保存:"+directory+"/"+pdffn,Toast.LENGTH_LONG).show();
					        File File = new File(directory,pdffn);		
							Intent intent = getFileIntent(File); 
					        startActivity(intent);
						}else{ 						
							mWebView.loadUrl(DownloadBlobFileJSInterface.getBase64StringFromBlobUrl(urlP));
						}
					}
				});				
	            //mWebView.loadUrl(DownloadBlobFileJSInterface.getBase64StringFromBlobUrl(url));	            			
			}else{
				DownloaderTask task=new DownloaderTask();  
				task.execute(url); 				
			}
		}  
    }

Note: The string returned by the web page JS has double quotes " wrapped around it, which needs to be removed.

Among them, DownloaderTask is the direct download method of reference 2. Note that download must be added to the A tag, otherwise an error will be reported:

<a href="./upload/dll.rar" download>dll.rar</a>

 Then, open the page that dynamically generates the pdf file in onCreate:

        double rndX=Math.random();
        urlx="?v="+String.valueOf(rndX);
        urlm=getString(R.string.mainUrl);//
        mWebView=(WebView)findViewById(R.id.webshow);        
        setWebStyle();
        mWebView.loadUrl(urlm + urlx);

7. Several variables and other explanations

// 定义于 extends Activity 与 onCreate 之间
	WebView mWebView;    // WebView 实例变量
	private String urlx=new String(""); //存放调用首页的参数等,如 double rndX=Math.random();       urlx="?v="+String.valueOf(rndX);

	private String urlw=new String("file:///android_asset/index.html"); //存放等待页面url
	private String urlm;//存放首页菜单的页面

	DownloadBlobFileJSInterface mDownloadBlobFileJSInterface = new DownloadBlobFileJSInterface(this);    // Blob 下载 js 接口定义
	private String urlP=new String("");    // 下载链接全局变量
	private static String pdffn=new String("");    // Blob 文件名全局变量

Among them, pdffn is used to store the default file name of the Blob link, because I am too lazy to move the input variable of DownloadBlobFileJSInterface, so I use this to pass it to the convertToGifAndProcess method to save the file name:

/**
		 * 转换成file
		 * @param base64
		 */
		private void convertToGifAndProcess(String base64) {
			String fileName =pdffn;// UUID.randomUUID().toString() + ".pdf";
			// System.out.println("3.convertToGifAndProcess:fileName:" + fileName);
			File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
			File gifFile = new File(directory, fileName);
			// System.out.println("4.convertToGifAndProcess:gifFile:" + gifFile);
			// Log.e("tag", "type=convertToGifAndProcess" + gifFile);
			saveFileToPath(base64, gifFile);
			// System.out.println("7.convertToGifAndProcess:mDownloadGifSuccessListener:" + mDownloadGifSuccessListener);
			// System.out.println("8.convertToGifAndProcess gifFile:" + gifFile);
			if (mDownloadGifSuccessListener != null) {
				// System.out.println("10.mDownloadGifSuccessListener 不为空到这里:" + mDownloadGifSuccessListener);
				mDownloadGifSuccessListener.ondownloadGifSuccess(gifFile.getAbsolutePath());
			}
		}

		/**
		 * 保存文件
		 * @param base64
		 * @param gifFilePath
		 */
		private void saveFileToPath(String base64, File gifFilePath) {
			// System.out.println("5.saveFileToPath gifFilePath.getAbsolutePath():" + gifFilePath.getAbsolutePath());
			try {
				byte[] fileBytes = Base64.decode(base64.replaceFirst(
					"data:application/pdf;base64,", ""), 0);
				FileOutputStream os = new FileOutputStream(gifFilePath, false);
				os.write(fileBytes);
				os.flush();
				os.close();
				Toast.makeText(mContext, "文件已保存:"+gifFilePath,Toast.LENGTH_LONG).show();
				// Log.e("tag", "type=saveFileToPath" + gifFilePath);
				// System.out.println("6.saveFileToPath FileOutputStream gifFilePath:" + gifFilePath);

			} catch (Exception e) {
				e.printStackTrace();
			}
		}

At this point, the complete process and key codes are completed. The following is the sample after running:

 

 

’-------------------------------

This note!

Next, we need to solve the problem of display width on the mobile phone

Guess you like

Origin blog.csdn.net/jessezappy/article/details/126264165