安卓调用PrinterShare实现无线打印功能

9,559 阅读9分钟

 最近项目需要用到蓝牙打印,百度了很多资料,关于安卓移动打印的文章都不是很详细。要知道自己去写一个蓝牙打印模块那可是一个比较大的工作量了,而且还需要对市面上的打印机进行兼容。这个我引用一篇文章大家看下:

一、Android客户端打印技术现状

云打印

在Android KitKat之前,Google推出云打印,用户将需要打印的数据上传到服务器并填写好联系信息,打印好的图片邮寄给用户。
这是一种商业模式而非技术手段。也就是说在Android KitKat之前,Google一直没有推出Android打印的技术方案。除了Google推出的云打印方案,还存在很多第三方公司,也提供类似的方案(参考:2)。

打印框架

KitKat发布,Google推出了打印框架。该框架作为需要使用打印服务的应用于实现打印服务的应用之间的接口,通过该框架方便了开发者对打印功能的使用。其中,需要使用打印服务的应用使用打印框架API申请服务,具体的打印服务由打印机原始设备生产生提供。本质上,该框架方便了应用打印功能的使用,但实际上打印中最为关键的打印数据流的创建仍然是打印机生产商提供的APK来完成。(参考:3)

第三方

除了各大打印机生产厂商提供的打印APK,还有部分第三方也提供具有打印功能的应用,比如 PrinterShare这款应用。该应用的实现原理不明,猜测可能是其获取到了各个厂商的PDL的支持情况,并且拥有特定PDL的开发参考数据。

市场上的产品

以金山公司的 WPS为例,该应用支持常用办公文件的打印,打印提供两种选项:第一,使用系统的打印服务,即之前提到的KitKat之后的打印框架;第二,生成.ps文件。需要说明的一点是,该文件的内容是PostScript,根据前面的描述可知,该文件并不被所有打印机支持。所以,该应用的做法是,通过使用打印框架提供打印功能,同时提供对部分打印机的支持。此外,通过搜索可以发现,目前市场上关于打印类的应用非常少。(参考:4)

二、可行方案

通过前面的了解,确定两种方案

  • 方式一:应用操作 excel 文件填入数据,打印功能由第三方应用实现
  • 方式二:使用PDL实现打印

方式一

  1. 应用更新并下载打印模板(.xsl文件)
  2. 应用通过第三方SDK操作模板文件,将相应的数据填入
  3. 打印目标文件生成完成后,用户通过第三方应用实施打印
    第三方应用:
    1)厂商提供的APK,支持某些型号的打印机(支持范围不广)
    2)第三方提供的APK,比如PrintShare(支持的范围较广,参考:5)
    关于打印机的选择,支持的打印机要求:
    a. 便于携带
    b. 插件支持
    c. 价格在一定范围

方式二

  1. 同方式一,但是下载的文件是.txt文件
  2. 应用将模板txt与数据拼合
  3. 应用生成打印数据流通过蓝牙发往打印机

比较

打印方式

打印效果

开发和维护难度

应用的使用难度

方式一

简单

比较复

方式二

不好

复杂

打印效果方面,方式一的打印效果几乎与通过PC打印效果一直,方式二的打印效果存在字体模糊、样式不能被完全体现等问题。
技术实现方面,方式一可利用现有的比价成熟的excel文件操作SDK,方式二需要处理格式转换、文件拼接以及打印数据生成等问题。

实际选用:方式二。

三、HP PCL 3 打印语言

具体内容参考手册:PCL3_developers_guide.pdf

下面简要介绍一下该语言的基础知识

  1. 改语言由指令构成,类似汇编语言,每种功能通过特定的指令实现。比如,字号的设置通过某条指令实现
  2. 该指令存在多个版本,目标打印机使用的版本是 PCL 3 GUI,其是 PCL的第三版,相对 PCL 3对图形打印有较好的支持,其语言指令是PCL 3的超集。

可以看到还是比较复杂的。今天我们就调用PrinterShare实现打印,打印工作交给它去做,我们把文件传给他就行,具体实现流程:

1、下载官方apk,不要去国内市场去下载,ps:我在国内市场上下载的apk包名跟官方不一样(/ □ \)com.dynamixsoftware.printershareoaj,最新版是这个com.dynamixsoftware.printershare

2、反编译apk,这个不用说了大家都会;

3、查看清单文件我们需要关注几点:

    1)、包名

    2)、相关activity这里有很多打印模式:图片、pdf、word、doc、xls、ppt等等,我打印的是txt文本,选择的是这个activity

4、撸代码。。。

    1)、把apk安装包copy到assets目录下

     2)、安装apk,检测是否已经安装:

public static boolean isInstallApp(Context context, String packageName) {
    return !RxDataUtils.isNullString(packageName) && RxIntentUtils.getLaunchAppIntent(context, packageName) != null;
}

     如果已经安装就去打印,这里就用到了我们上面解析清单文件的内容了,设置包名、动作传递、文件类型、设置数据:

//如果是7.0及以上使用文件共享,不能直接传路径过去,下面有FileProvider使用说明
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {   new Thread({        File shareFilePath = new File(filesDir, "external_files");   
     shareFilePath .mkdir();        
File shareFile = new File(shareFilePath.getPath(), "test.txt") ;
//把sd卡根目录的测试文件拷贝到App的目录下,拷贝文件的方法就可以百度
 FileUtils.copyFile( Environment.getExternalStorageDirectory().getPath() + "/test.txt", shareFile .getPath());   Uri uri = FileProvider.getUriForFile(this, "你的APP包名" + ".fileprovider",  shareFile  );      
  ComponentName comp = ComponentName("com.dynamixsoftware.printershare",           "com.dynamixsoftware.printershare.ActivityPrintDocuments");    
   Intent intent =new Intent();           
   intent.setComponent(comp); 
   intent.setAction("android.intent.action.VIEW"); 
//授予临时权限
  intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
  intent.setType("text/plain");
   intent.setData(uri);      
   intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);  
   startActivity(intent); 
   }).start();
}else{    
 ComponentName comp = new ComponentName("com.dynamixsoftware.printershare","com.dynamixsoftware.printershare.ActivityPrintDocuments");Intent intent = new Intent();
intent.setComponent(comp);
intent.setAction("android.intent.action.VIEW");
intent.setType("text/plain");
intent.setData(Uri.parse(Environment.getExternalStorageDirectory().getPath()+"/test.txt"));
startActivity(intent);
}

对于安卓7.0及之后的处理方案:res目录新建xml文件夹,放置file_paths_public文件

里面的代码是这样

<?xml version="1.0" encoding="utf-8"?>
<paths> 
<files-path       
  name="share"      
  path="external_files"/>
</paths>

然后清单文件配置Provider

<provider    
android:name="androidx.core.content.FileProvider"  
  android:authorities="你的APP包名.fileprovider" 
  android:exported="false"  
  android:grantUriPermissions="true">   
 <meta-data       
 android:name="android.support.FILE_PROVIDER_PATHS"   
 android:resource="@xml/file_paths_public" />
</provider>

ps:这里我直接在sd卡跟目录放置了一个test文本。

关于ComponentName大家可以查下资料,可以打开另一个应用的组件。

 如果没有安装就把文件拷贝出来再安装:

从assets里面copy文件:

public File getAssetFileToCacheDir( String fileName) {
    try {
        File cacheDir = FileUtil.getCacheDir(MyApp.getApplictaion());
        final String cachePath = cacheDir.getAbsolutePath()+ File.separator + fileName;
        InputStream is = MyApp.getApplictaion().getAssets().open(fileName);
        File file = new File(cachePath);
        file.createNewFile();
        FileOutputStream fos = new FileOutputStream(file);
        byte[] temp = new byte[1024];

        int i = 0;
        while ((i = is.read(temp)) > 0) {
            fos.write(temp, 0, i);
        }
        fos.close();
        is.close();
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mRxDialogLoading.cancel();
            }
        });
        return file;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

安装:

public static void InstallAPK(Context context, String APK_PATH) {//提示安装APK
    Intent i = new Intent(Intent.ACTION_VIEW);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    i.setDataAndType(Uri.parse("file://" + APK_PATH), "application/vnd.android.package-archive");
    context.startActivity(i);
}

到此整个调用PrinterShare打印就完成了我们看下效果:

点击打印提示安装apk:

安装成功,再点击“打印”按钮

继续,这个时候提示需要加载渲染库,点击是,ps:慢慢等它加载完~~~

最后预览图:

test原文本是这样的:

吐槽,PrinterShare的UI是真的丑~~~~~

分割线--------------------------------------------------------------------------------------------------------------------

更新部分:

上面部分是打印txt文本,然而实际需求肯定不是打印txt,打印txt文字排版很不好字体也不好控制,而且特殊字符无法显示出来,一般都是打印pdf、word、html。。。

我实际项目需求是打印一个罚单,还算简单,有的是打印电子账单。。。一开始想用itext把文本内容转pdf去打印,ps:itext功能真的是很强大,pc上表现很强,有兴趣的朋友可以去研究研究,分5和7,一个社区版本一个商业版本,地址~~~,可是后来想想首先(1)、加jar包,apk体积增大,(2)性能,安卓平台和pc平台这个都懂的,资源紧缺啊!java版本的在大部分是在pc上的项目,虽然国外有大牛把项目移植到安卓上,但是直接拿到安卓上去用还是有很多坑。。。(3)结合自身项目需求,没必要~~~

好了进入正题

1、新建一个html放到assets目录下(或者sd卡目录下在或者服务器上也可以)

这个是我需要打印的内容,暂时就这样,比较简单不花俏,后面还需要加个印章~~~大致效果就是这样。

ps:为了不打扰前端大佬我自己动手写的,,我不会前端不会前端啊!~~~~~这个可以让前端根据打印内容写个漂亮的html。

2、找到我们上面的清单文件这次我们使用的是ActivityWeb

3、撸代码

注意:这里跟我们上面打印txt不一样了,activity组件、Type类型、Data都不一样

URI转换需要注意,前面需要加个“file:///”,一开始我就写了个本地sd卡路径,怎么都显示不了数据,后来发现ActivityWeb是先通过WebView加载网页显示再去打印的,WebView加载网页和本地html都知道吧,这里就不详说了。。。

//如果是7.0及以上使用文件共享,不能直接传路径过去
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {   new Thread({        
File shareFilePath = new File(filesDir, "external_files");   
     shareFilePath .mkdir();       
 File shareFile = new File(shareFilePath.getPath(), "test2.html");
//把sd卡根目录的测试文件拷贝到App的目录下,拷贝文件的方法就可以百度
 FileUtils.copyFile( Environment.getExternalStorageDirectory().getPath() + "/test2.html", shareFile.getPath ())   
Uri uri = FileProvider.getUriForFile(this, "你的APP包名" + ".fileprovider",  shareFile  ) ;      
 ComponentName comp = ComponentName("com.dynamixsoftware.printershare",  "com.dynamixsoftware.printershare.ActivityPrintDocuments");    
    Intent intent =new Intent();           
    intent.setComponent(comp); 
    intent.setAction("android.intent.action.VIEW");     
 //授予临时权限
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    intent.setType("text/html");     
  intent.setData(uri);      
   intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);  
   startActivity(intent); 
   }).start();
}else{    
 ComponentName comp = new ComponentName("com.dynamixsoftware.printershare","com.dynamixsoftware.printershare.ActivityWeb");
Intent intent = new Intent();
intent.setComponent(comp);
intent.setAction("android.intent.action.VIEW");
intent.setType("text/html");
intent.setData(Uri.parse("file:///"+Environment.getExternalStorageDirectory().getPath()+"/test2.html"));
startActivity(intent);
}

ps:这里我直接在sd卡目录放了个test2.html,实际需求场景还需要进一步考虑~~~~~反正先完成功能就ok~~~~~如果html是放在服务器那么就传一个打印html的地址。

好了,看一下效果吧:

点击"打印"跳转到预览

OK,PrinterShare功能是不是很强大?文本、pdf、doc、word、网页、表格、图片等等都可以进行打印,大家可以自行研究其他功能~~~匹配好它清单文件的过滤条件实现无线打印功能。