基于 jq 实现拖拽上传 APK 文件,js解析 APK 信息

技术栈

  • jquery
  • 文件上传:jquery.fileupload,github 文档
  • apk 文件解析:app-info-parser,github 文档

参考:前端解析ipa、apk安装包信息 —— app-info-parser

支持功能

  • 点击或拖拽上传 apk 文件
  • 校验文件类型及文件大小
  • js 解析 apk 文件信息展示并通过上传接口提交给后端
  • 支持上传过程中取消上传
  • 支持上传成功显示上传信息
  • 支持解析、上传等友好提示
  • 支持从历史记录(所有已上传文件)中选择一个
  • 支持假文件处理,比如 .txt 文件改为 .apk 文件
  • 上传进度实时更新,百分比,B/s
  • 拖拽进入拖拽区时,高亮显示

demo 预览

说明

由于上传接口需要后端接口的支持,所以没法用静态页面展示完整的交互。因此,在这儿放个预览图。

demo

为了避免 gif 图太大,只录屏了点击上传成功的情况。其他情况没录屏,可自行下载 demo ,搭建后端环境,模拟上传接口实现。demo 中用 php 语言模拟实现了上传接口。源码地址

难点

  • js 解析 APK 文件信息
  • 拖拽上传,点击上传和拖拽上传绑定到一起
  • 在上传之前不知道 APK 文件信息,需要执行上传操作过程中将解析的文件信息作为参数放到上传接口中
  • 上传过程中取消上传
  • 假文件解析错误处理,js 监控控制台错误

实现

1. js 解析 APK 文件信息

经过查阅,了解到 APK 文件的本质就是一个压缩包,其中包含一堆XML文件,资产和类文件。javascript 解析 APK 文件信息,要做的就是先解压,然后读取其中相关的文件,就能得到文件信息了。

难点在解压上,参考的基本都需要借助 node 环境。由于现在维护的系统是基于 jquery 环境的。所以最终采用了
前端解析ipa、apk安装包信息 —— app-info-parser 该文的方案,很好的解决了问题。在此非常感谢该作者。

 // apk 文件解析
var parser = new AppInfoParser(data.files[0]);
parser.parse().then(function(result) {uploadMod.doms.uploadErr.html('');var appInfo = result.application || {};var formAppInfo = {name: appInfo.label ? (Array.isArray(appInfo.label) ? appInfo.label[0] : appInfo.label) : '',package: result.package,version: result.versionName,version_code: result.versionCode};// 省略其他操作代码...
}).catch(function (err) {uploadMod.doms.uploadErr.html('文件解析错误,请重新上传');
});

说明:

  • 由于 app-info-parser 底层用了 async 语法,在 IE 下是不兼容的。在 firefox、chrome 下是正常的。
  • 上传假 APK 文件,不能处理,js 脚本会报错:File format is not recognized.。目前想到的解决方案是 js 监听错误,然后进行处理。若有更好想法的,欢迎@我。在此提前感谢。
// console.error() 监控处理
consoleError = window.console.error;
window.console.error = function () {consoleError && consoleError.apply(window, arguments);for (var info in arguments) {if (arguments[info] == 'File format is not recognized.') {$('#app_parse').html('<p style="color:red;">由于您上传了非真正的 APK 文件,导致脚本解析出错,即将重新刷新页面,给您带来不好的体验,敬请原谅</p>');setTimeout(function () {history.go(0);}, 3000);return false;}}
};

为了避免页面其它错误,导致脚本无法运行,因此做了页面刷新。

2. 拖拽上传,点击上传和拖拽上传绑定到一起

在做这个功能前,想到拖拽上传可以利用 H5 的拖拽功能及原生 js 的 file 文件上传实现,但需要处理兼容性问题。后来想到系统中已经引入了 jquery.fileupload 库,于是特地翻阅了文档,支持拖拽上传。因此采用该库实现拖拽上传功能。

html 布局如下:

<div class="upload-area" id="upload_area"><i class="icon-upload"></i><p class="upload-text">将安装包拖拽至此上传或 <em>选择文件</em></p><p class="upload-tip">支持 APK 文件,最大不超过 300 MB</p><input type="file" id="upload_input" name="file" accept="application/vnd.android.package-archive" data-size="300"/>
</div>

如何将 拖拽、点击 一起处理,用一个上传方法实现,而不是分开需要实现2遍?

想法是,点击外层容器,触发 input 点击事件。前提是需要实现 input 点击事件,并且阻止冒泡事件,因为外层也有点击事件。

$('body').on('click', '#upload_input', function (e) {e.stopPropagation();uploadMod.methods.fileUpload();
}).on('click drop dragenter dragover dragleave', '#upload_area', function(e) {e.preventDefault();uploadMod.doms.uploadErr.html('');switch (e.type) {case 'click':$('#upload_input').val(null);$('#upload_input').click();break;case 'drop':uploadMod.doms.uploadArea.removeClass('active');$('#upload_input').val(null);uploadMod.methods.fileUpload();break;case 'dragenter':case 'dragover':uploadMod.doms.uploadArea.addClass('active');break;case 'dragleave':uploadMod.doms.uploadArea.removeClass('active');break;}
})

实现了拖拽进入高亮、远离恢复。需要注意的是,$('#upload_input') 不能用缓存的变量。否则会导致二次点击上传失效,无法触发点击打开文件窗口。以及此时拖拽上传一个正确的文件,会触发 2 次文件上传。发送 2 次上传接口。感兴趣的朋友可以自己用缓存的试一下。

案例复现:

  • 点击假的内容为空的 apk 文件,会提示:文件尺寸不对。
  • 此时,第二次点击,无法触发 input 的点击事件。反复多次依然无效。
  • 此时,通过拖拽上传,能够正常执行,但是会触发 2 次上传处理,解析 2 次文件,发送 2 次上传接口请求。

3. 在上传之前不知道 APK 文件信息,需要执行上传操作过程中将解析的文件信息作为参数放到上传接口中

之前做过的上传,是在上传前就已经知道在上传时需要提交的额外参数值。

$('#upload_input').fileupload({url: 'http://localhost:80/jq-drag-upload-apk-parse/upload.php',dataType: 'json',formData: params, // params 为 js 对象,是需要提交的参数multi: false,// 省略....
})

但现在,在上传前是不知道参数值的,需要在执行上传操作,拿到上传文件信息,并解析出上传文件的信息,然后将解析信息做为参数值放到上传请求中。那怎么做呢,研究了很久,才找到。

$('#upload_input').fileupload({url: 'http://localhost:80/jq-drag-upload-apk-parse/upload.php',dataType: 'json',formData: params, // params 为 js 对象,是需要提交的参数multi: false,add: function (e, data) {// 省略文件类型及大小校验// 省略 APK 文件解析及进度条等的 UI 初始化$(e.target).fileupload('option','formData',formAppInfo // APK 解析出的数据);data.submit();},// 省略....
})

4. 上传过程中取消上传

这个相对比较容易。利用上传回调中的 data.abort() 即可实现。需要处理的是,在 add() 方法里需要先在外层缓存一下 data,才方便对其的调用。

$('#upload_input').fileupload({url: 'http://localhost:80/jq-drag-upload-apk-parse/upload.php',dataType: 'json',formData: params, // params 为 js 对象,是需要提交的参数multi: false,add: function (e, data) {// 省略文件类型及大小校验// 省略 APK 文件解析及进度条等的 UI 初始化// 外层缓存,方便调取消上传uploadMod.uploadXHR = data;$(e.target).fileupload('option','formData',formAppInfo // APK 解析出的数据);data.submit();},fail: function(e, data) {if (data.errorThrown == 'abort') {uploadMod.doms.uploadErr.html('已取消上传,可重新上传');} else {uploadMod.doms.uploadErr.html('上传失败,请重新上传');}},// 省略....
})
$('body').on('click', '#upload_cancel', function () {uploadMod.uploadXHR.abort();
})

5. 文件上传的主要代码

fileCheck: function(e, data) {// 文件格式及文件大小校验var acceptFileTypes = uploadMod.doms.uploadInput.attr('accept');var supportFileTypes = ['apk']; // 通过name后缀再校验一次,避免获取不到type的情况var maxSize = uploadMod.doms.uploadInput.data('size') * 1024 * 1024; // 单位mb,需要转换为bvar fileTypeFlag = data.originalFiles.every(function(item) {if (item.type) {return acceptFileTypes.indexOf(item.type) > -1;} else {var splits = (item.name || file).split('.');var fileType = splits[splits.length - 1];return supportFileTypes.indexOf(fileType) > -1;}});if (!fileTypeFlag) {uploadMod.doms.uploadErr.html('请上传 APK 文件');return false;}var fileSizeFlag = data.originalFiles.every(function(item) {return item.size > 0 && item.size <= maxSize;});if (!fileSizeFlag) {data = {};uploadMod.doms.uploadErr.html('文件大小不正确');return false;}uploadMod.doms.progressWrap.show();var $appParse = uploadMod.doms.progressWrap.find('.app-parse'),$progressCon = uploadMod.doms.progressWrap.find('.con');$appParse.show();$progressCon.hide();// apk 文件解析var parser = new AppInfoParser(data.files[0]);parser.parse().then(function(result) {uploadMod.doms.uploadErr.html('');var appInfo = result.application || {};var formAppInfo = {name: appInfo.label ? (Array.isArray(appInfo.label) ? appInfo.label[0] : appInfo.label) : '',package: result.package,version: result.versionName,version_code: result.versionCode};// 进度条初始化$appParse.hide();$progressCon.show();if (result.icon) {uploadMod.doms.progressWrap.find('.icon-app').css('background-image', 'url("' + result.icon + '")');}uploadMod.doms.progressWrap.find('.name').html(formAppInfo.name);uploadMod.doms.progressWrap.find('.package').html(formAppInfo.package);uploadMod.doms.progressWrap.find('.version').html(formAppInfo.version);uploadMod.doms.progressWrap.find('.version-code').html(formAppInfo.version_code);uploadMod.doms.progressWrap.find('.progress').css('width', 0);uploadMod.doms.progressWrap.find('.num').html(0);uploadMod.doms.progressWrap.find('.size').html(0);// 设置上传接口参数uploadMod.uploadXHR = data;$(e.target).fileupload('option','formData',formAppInfo);data.submit();}).catch(function (err) {uploadMod.doms.progressWrap.hide();uploadMod.doms.uploadErr.html('文件解析错误,请重新上传');data.abort();});// console.error() 监控处理consoleError = window.console.error;window.console.error = function () {consoleError && consoleError.apply(window, arguments);for (var info in arguments) {if (arguments[info] == 'File format is not recognized.') {$('#app_parse').html('<p style="color:red;">由于您上传了非真正的 APK 文件,导致脚本解析出错,即将重新刷新页面,给您带来不好的体验,敬请原谅</p>');setTimeout(function () {history.go(0);}, 3000);return false;}}};
},
fileUpload: function(el) {$('#upload_input').fileupload({url: 'http://localhost:80/jq-drag-upload-apk-parse/upload.php',dataType: 'json',multi: false,add: uploadMod.methods.fileCheck,paste: function () { return false; },done: function(e, data) { // 上传成功回调var result = data.result;if (result && result.flag && result.data) {uploadMod.doms.uploadErr.html(result.msg || '上传成功');uploadMod.data.selectedAPK = result.data;uploadMod.methods.renderHistory(result.data);} else {uploadMod.doms.progressWrap.hide();uploadMod.doms.uploadErr.html(result.msg || '上传失败');}},fail: function(e, data) {if (data.errorThrown == 'abort') {uploadMod.doms.uploadErr.html('已取消上传,可重新上传');} else {uploadMod.doms.uploadErr.html('上传失败,请重新上传');}},progressall: function(e, data) {var progress = parseInt(data.loaded / data.total * 100, 10);uploadMod.doms.progressWrap.find('.progress').css('width', progress + '%');uploadMod.doms.progressWrap.find('.num').html(progress);uploadMod.doms.progressWrap.find('.size').html(bytesToSize(data.bitrate));function bytesToSize(bit) {if (bit === 0) return '0 B';var bytes = bit / 8;var k = 1024,sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],i = Math.floor(Math.log(bytes) / Math.log(k));return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];}}})
},

php 环境简单搭建

  • 下载 xampp 集成环境包进行安装
  • 在 demo 项目解压拷贝到安装目录下的 htdocs 的目录下,我的目录是 C:\xampp\htdocs\jq-drag-upload-apk-parse
  • 由于 php 上传有限制,需要改文件C:\xampp\php\php.ini,需要修改的点:
    • max_execution_time = 0,默认 30 秒,0 为无限制
    • post_max_size = 500M,默认 2M
    • upload_max_filesize = 100M,默认 8M
    • ps:参考PHP上传大小限制修改
  • 最后点击安装目录下的(C:\xampp)的 xampp.control.exe 打开界面,在打开界面中,将 Apache 对应的 Actions 开启
  • 在浏览器窗口输入http://localhost/jq-drag-upload-apk-parse/index.html
  • 即可完整查看 demo 效果
  • 源码地址

转载于:https://www.cnblogs.com/EnSnail/p/10439281.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/363706.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

调试以了解终结器

这篇文章涵盖了Java内置概念之一&#xff0c;称为Finalizer 。 这个概念实际上是众所周知的&#xff0c;也是众所周知的&#xff0c;这取决于您是否有足够的时间来仔细研究一下java.lang.Object类。 就在java.lang.Object本身中&#xff0c;有一个名为finalize&#xff08;&…

Zookeeper实现注册与发现

1.Zookeeper的数据模型 (1) Zookeeper的数据模型&#xff0c;类似于树形结构&#xff1a; (2) Zookeeper的每一个节点成为称为Znode&#xff0c;主要用来存储数据。 data : 存储数据信息。acl : 记录Znode的访问权限。child : 当前节点的子节点引用。stat &#xff1a;包含Zn…

class 命名规范

本文是从简书复制的, markdown语法可能有些出入, 想看"正版"和更多内容请关注 简书: 小贤笔记 注: 文章摘自 penggelies07- 简书, super晴天 - CSDN 常见class关键词 布局类&#xff1a;header, footer, container, main, content, aside, page, section 包裹类&am…

web策略类游戏开发(四)一个可以承载万人在线的架构

web策略类游戏开发(四)一个可以承载万人在线的架构 Webgame现在已经开始需要进入大统一服务器时代&#xff0c;每个游戏区域容纳的玩家数量将从现在的几万人发展到几十万人&#xff0c;因此在新的背景下&#xff0c;webgame如何处理大量用户的请求将成为问题。目前一台asp.net做…

复制物料时不复制安全库存

1.打开bos&#xff0c;选择物料-功能控制 2.把允许复制去掉 转载于:https://www.cnblogs.com/RogerLu/p/10441588.html

CSS实现水平垂直居中

1、需求分析 子元素在父元素中水平垂直居中 2、技术分析 基础的css、html 3、详细分析 如图: 3.1 HTML部分 如图所示&#xff0c;大边框内包含一个小边框两部分&#xff0c;设置一个父元素div和一个子元素div。 <div class"container">父元素<div class…

从Java连接到Cassandra

在我的帖子Hello Cassandra中 &#xff0c;我研究了如何下载Cassandra NoSQL数据库并使用cqlsh连接到Cassandra数据库。 在本文中&#xff0c;我将介绍从Java客户端连接到Cassandra数据库的基础知识。 尽管有几种可用于从Java访问Cassandra数据库的 框架 &#xff0c;但我将在…

Django---Model操作

一、字段 1 AutoField(Field)2 - int自增列&#xff0c;必须填入参数 primary_keyTrue3 4 BigAutoField(AutoField)5 - bigint自增列&#xff0c;必须填入参数 primary_keyTrue6 7 注&#xff1a;当model中如果没有自增列&#xff0c;则自动会创建…

Vuex的第一次接触

前言&#xff1a;最近在做Vue实现去哪网&#xff0c;想要实现在城市列表页面&#xff0c;点击某个城市的时候&#xff0c;主页的头部的城市会随着改变&#xff0c;就是首页和城市页面有共用的数据要分享&#xff0c;这里使用Vuex 1. Vuex是什么&#xff1f; 是Vue官方推荐的数…

java IO流小结

Java流操作有关的类或接口&#xff1a; Java流类图结构&#xff1a; 流的概念和作用 流是一组有顺序的&#xff0c;有起点和终点的字节集合&#xff0c;是对数据传输的总称或抽象。即数据在两设备间的传输称为流&#xff0c;流的本质是数据传输&#xff0c;根据数据传输特性将流…

华为android是什么型号,华为手机机型众多,目前这几款最值得入手

华为手机机型众多&#xff0c;目前这几款最值得入手2020-09-22 15:00:033点赞0收藏0评论华为手机可以说是国家手机的代名词。受某种感情的影响&#xff0c;很多人都用华为取代了iPhone。为了表达感情&#xff0c;很多人也纷纷效仿&#xff0c;购买华为手机。但我想说的是支持华…

pt-online-schema-change VS oak-online-alter-table【转】

前言 在上篇文章中提到了MySQL 5.6 Online DDL&#xff0c;如果是MySQL 5.5的版本在DDL方面是要付出代价的&#xff0c;虽然已经有了Fast index Creation&#xff0c;但是在添加字段还是会锁表的&#xff0c;而且在添加删除辅助索引是会加S锁&#xff0c;也就是无法进行写操作。…

vue命令行错误处理

全局安装vue/cli时&#xff1a;npm install -g vue/cli &#xff08;1&#xff09;Error: EACCES: permission denied, access /usr/local/lib/node_modules/vue/cli 原因: 执行命令时没有获得管理员权限 解决办法: 在命令前面加上sudo即可.然后输入电脑的管理员密码操作即可…

RAC(ReactiveCocoa)介绍(一)

最近在学习RAC&#xff0c;之前在iOS工作中&#xff0c;类之间的传值&#xff0c;无非是block、delegate代理、KVO和Notification等这几种方法。在RAC中&#xff0c;同样具备替代block、delegate代理、KVO和Notification&#xff0c;UI target、定时器timer、数据结构等各种方式…

一段简单的html 5 音频,5个用于处理HTML5音频的库和API

在过去的几个月中&#xff0c;我遇到了许多不同的库&#xff0c;它们利用了相对较新的HTML5 Audio API以及更著名的HTML5 Audio Element及其更简单的API。我以为我会在本文中分享这些库中的一小部分&#xff0c;以向您展示如果选择创建需要操纵声音文件的游戏或应用程序&#x…

WinAPI: SetRect 及初始化矩形的几种办法

本例分别用五种办法初始化了同样的一个矩形, 运行效果图:unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls;typeTForm1 class(TForm)Button1: TButton;Button2: TButton;Button3: TButton;Button4: TBu…

备忘录——通过RVA计算文件位置

备忘录——通过RVA计算文件位置 原创&#xff1a;Anders Liu 摘要&#xff1a;本文介绍了如何通过PE文件中某一项的RVA来计算其在文件中的位置。 参考文献 ECMA-335——Common Language Infrastructure (CLI) 4th Edition, June 2006 范畴 该备忘录描述了在分析PE&#xff08;可…

中后端管理系统前后分离、前端框架的实现拙见

一、实现思路 在实践中后台管理系统的前后端分离时&#xff0c;往往会因为业务量的增加使其前端项目难以维护&#xff0c;以及打包时间不理想&#xff0c;还有业务系统与框架之间区分不在明显。本文是本人从另一个角度提出的一种解决方案&#xff0c;希望各位提出宝贵的建议。…

初见mobX

先看如下的代码 const {observable} mobox; const {observer}mobxReact; const {Component}React; const appStateobservable({count:0 }) appState.incrementfunction(){this.count } appState.decrementfunction(){this.count-- } observer class Counter extends Component{…

【留言板】可编辑输入框操作总结

闲暇之余&#xff0c;用于加深自己对基础的了解&#xff0c;徒手撸了一个留言板&#xff1a;输入框。废话少说&#xff0c;进入正题。简陋的效果如下(下载代码)&#xff1a; 一、定义需求 可输入文本&#xff0c;以及插入表情。兼容性&#xff1a;IE与标准浏览器 二、详细设计…