热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

技术栈jquery文件上传:jquery.fileupload,github文档apk文件解析:app-info-parser,github文档参考:前端解析ipa

技术栈

  • 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('

由于您上传了非真正的 APK 文件,导致脚本解析出错,即将重新刷新页面,给您带来不好的体验,敬请原谅

'); setTimeout(function () { history.go(0); }, 3000); return false; } } };

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

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

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

html 布局如下:

将安装包拖拽至此上传或 选择文件

支持 APK 文件,最大不超过 300 MB

如何将 拖拽、点击 一起处理,用一个上传方法实现,而不是分开需要实现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,需要转换为b
    var 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('

由于您上传了非真正的 APK 文件,导致脚本解析出错,即将重新刷新页面,给您带来不好的体验,敬请原谅

'); 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 效果
  • 源码地址

推荐阅读
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • Hibernate延迟加载深入分析-集合属性的延迟加载策略
    本文深入分析了Hibernate延迟加载的机制,特别是集合属性的延迟加载策略。通过延迟加载,可以降低系统的内存开销,提高Hibernate的运行性能。对于集合属性,推荐使用延迟加载策略,即在系统需要使用集合属性时才从数据库装载关联的数据,避免一次加载所有集合属性导致性能下降。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • Html5-Canvas实现简易的抽奖转盘效果
    本文介绍了如何使用Html5和Canvas标签来实现简易的抽奖转盘效果,同时使用了jQueryRotate.js旋转插件。文章中给出了主要的html和css代码,并展示了实现的基本效果。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • JavaScript和HTML之间的交互是经由过程事宜完成的。事宜:文档或浏览器窗口中发作的一些特定的交互霎时。能够运用侦听器(或处置惩罚递次来预订事宜),以便事宜发作时实行相应的 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • Node.js学习笔记(一)package.json及cnpm
    本文介绍了Node.js中包的概念,以及如何使用包来统一管理具有相互依赖关系的模块。同时还介绍了NPM(Node Package Manager)的基本介绍和使用方法,以及如何通过NPM下载第三方模块。 ... [详细]
  • Commit1ced2a7433ea8937a1b260ea65d708f32ca7c95eintroduceda+Clonetraitboundtom ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • 本文详细介绍了git常用命令及其操作方法,包括查看、添加、提交、删除、找回等操作,以及如何重置修改文件、抛弃工作区修改、将工作文件提交到本地暂存区、从版本库中删除文件等。同时还介绍了如何从暂存区恢复到工作文件、恢复最近一次提交过的状态,以及如何合并多个操作等。 ... [详细]
  • 本文介绍了在CentOS 6.4系统中更新源地址的方法,包括备份现有源文件、下载163源、修改文件名、更新列表和系统,并提供了相应的命令。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有