1. 程式人生 > >android 通過wifi呼叫印表機

android 通過wifi呼叫印表機

          android是可以通過wifi呼叫印表機列印圖片或者文件的,在API19之前,呼叫印表機是通過Socket通訊然後列印東西的,Socket是比較原始的通訊模式,也是相對比較底層的,一般通過埠連線是可以連線任意兩臺機器進行資料傳輸並操作的,印表機也不例外。但是API19之後,android有了自己的列印框架,就是Kit Kat Print,這個列印框架可以直接生成pdf或者列印圖片或者文件,這裡的文件就是指類似於word之類的東西,直接操作View繪製文件,然後生成pdf或者通過wifi呼叫印表機列印。

        這算是android的邊緣技術,畢竟用這個的並不是很多,大致說下。

       1.Kit Kat Print這個列印框架只提供列印功能,至於搜尋功能也不知道是沒實現,還是根本就沒有。我用的方式是先下載Hp print service 外掛,這是個移動端的service型別的app,提供搜尋wifi下同一網段的印表機,安裝後沒有介面。如果沒用這個外掛的話,會顯示一直在搜尋中,安裝完成後,在設定裡面就會有印表機的選項了,某些機型裡面是在設定裡面,比如leX820,有些機型乾脆就沒有,比如Vivo,但是仍然能夠掃描的到印表機。

       Hp print service外掛可以直接在各種應用商店裡面找,很容易搜得到的。

       2.然後說下程式碼如何實現,一般用的到也就列印圖片或者文件,先說列印圖片,程式碼如下:

private void doPhotoPrint() {
		//申請sd卡許可權
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
                        Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS},
                4);

		//例項化類
        PrintHelper photoPrinter = new PrintHelper(this);
        photoPrinter.setScaleMode(PrintHelper.SCALE_MODE_FIT);//設定填充的型別,填充的型別指的是在A4紙上列印時的填充型別,兩種模式

        //列印
        Bitmap bitmap = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory() + "/doctor/testprint.jpeg");
        photoPrinter.printBitmap("jpgTestPrint", bitmap);//這裡的第一個引數是列印的jobName
}
      裡面有部分需要解釋下,填充型別ScaleMode一共兩種,原始碼中的解釋如下:
    /**
     * image will be scaled but leave white space
     */
    public static final int SCALE_MODE_FIT = 1;

    /**
     * image will fill the paper and be cropped (default)
     */
    public static final int SCALE_MODE_FILL = 2;
        仍然解釋的不是很明朗,解釋下:Fit模式下,你所列印的圖片會縮放至A4紙能夠顯示所有的圖片內容;Fill模式下,你所列印的圖片會把列印的圖片拉伸直到某條邊能夠和對應的A4紙的邊長度一致。

       另外要注意的是printBitmap的第一個引數,如果列印2次以上的時候,每次傳進去的引數最好不一致,這樣不會列印錯。

       2.比較麻煩的是列印文件,列印文件的話就比較適合平常用電腦操作列印文件了,就類似於在自定義View上面畫東西一樣,先給出列印的方法:

 //print picture in document
private void onPrintDoc(String jobName, String absPicturePath){
	PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
	PrintAttributes.Builder builder = new PrintAttributes.Builder();
	builder.setColorMode(PrintAttributes.COLOR_MODE_COLOR);//設定色彩模式,黑白或者彩色,不管用
	builder.setMinMargins(new PrintAttributes.Margins(300, 200, 300, 200));//設定頁邊距,就是列印的有效區域距離紙的邊緣部分的距離,不管用
//  builder.setDuplexMode(DUPLEX_MODE_SHORT_EDGE);//設定按照橫著列印還是豎著列印,不管用
	PrintAttributes.MediaSize temp = PrintAttributes.MediaSize.ISO_A4;//設定列印的紙張的型別,比如A4,A8等,不管用
	temp.asLandscape();
	Log.i("blb", "--------is portrait:" + temp.isPortrait());
	builder.setMediaSize(temp);//這個也不管用
//  builder.setResolution(new PrintAttributes.Resolution("white", "whiteRadish", 150, 150));//設定列印紙的解析度,這個也不管用
	MyPrintAdapter myPrintAdapter = new MyPrintAdapter(this, absPicturePath);
	printManager.print(jobName, myPrintAdapter, builder.build());
}
        看到寫了那麼多引數其實一個都不管用,因為hp print service 並不接受這些引數設定,所以設定啥都不行。另外印表機的解析度是可以設定的,比如300dpi,600dpi,如果不明白可以參考我之前寫的畫素的轉換文章。

        前面的是呼叫的方式,導一下包就行了,沒有比較麻煩的,涉及到一個MyPrintAdapter這個需要自己寫,這裡需要大概說下,在android裡面,只要是adapter的名字的類,一般都是處理資料,或者回掉,或者各種中間過程轉換的,這個MyPrintAdapter也類似,先看下示例程式碼:

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.pdf.PdfDocument;
import android.graphics.pdf.PdfDocument.PageInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.pdf.PrintedPdfDocument;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by whiteRadish on 2017/6/14.
 */

@TargetApi(Build.VERSION_CODES.KITKAT)
public class MyPrintAdapter extends PrintDocumentAdapter {

    Context mContext;
    private int pageHeight;
    private int pageWidth;
    public PdfDocument myPdfDocument;
    public int totalpages = 1;//設定一共列印一張紙

    public MyPrintAdapter(Context context) {//這裡傳各種需要的引數就行
        this.mContext = context;
    }

    @Override
    public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal,
                         LayoutResultCallback callback,
                         Bundle metadata) {

        Log.i("blb", "--------run onLayout");
        myPdfDocument = new PrintedPdfDocument(mContext, newAttributes); //建立可列印PDF文件物件

        pageHeight =
                newAttributes.getMediaSize().getHeightMils() / 1000 * 72; //設定尺寸,為什麼是1000 * 72, 72dpi
        pageWidth =
        newAttributes.getMediaSize().getWidthMils() / 1000 * 72;

        if (totalpages > 0) {
            PrintDocumentInfo.Builder builder = new PrintDocumentInfo
                    .Builder("whiteRadish")
                    .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
                    .setPageCount(totalpages);  //構建文件配置資訊

            PrintDocumentInfo info = builder.build();
            callback.onLayoutFinished(info, true);
        } else {
            callback.onLayoutFailed("Page count is zero.");
        }
    }

    @Override
    public void onWrite(final PageRange[] pageRanges, final ParcelFileDescriptor destination, final CancellationSignal cancellationSignal,
                        final WriteResultCallback callback) {

        Log.i("blb", "--------run onWrite");

        for (int i = 0; i < totalpages; i++) {
            if (pageInRange(pageRanges, i)) //保證頁碼正確
            {
                PageInfo newPage = new PageInfo.Builder(pageWidth,
                        pageHeight, i).create();//建立對應的Page

                PdfDocument.Page page =
                        myPdfDocument.startPage(newPage);  //建立新頁面

                if (cancellationSignal.isCanceled()) {  //取消訊號
                    callback.onWriteCancelled();
                    myPdfDocument.close();
                    myPdfDocument = null;
                    return;
                }
                drawPage(page, i);  //將內容繪製到頁面Canvas上
                myPdfDocument.finishPage(page);
            }
        }

        try {
            myPdfDocument.writeTo(new FileOutputStream(
                    destination.getFileDescriptor()));
        } catch (IOException e) {
            callback.onWriteFailed(e.toString());
            return;
        } finally {
            myPdfDocument.close();
            myPdfDocument = null;
        }

        callback.onWriteFinished(pageRanges);
    }

    private boolean pageInRange(PageRange[] pageRanges, int page) {
        for (int i = 0; i < pageRanges.length; i++) {
            if ((page >= pageRanges[i].getStart()) &&
                    (page <= pageRanges[i].getEnd()))
                return true;
        }
        return false;
    }

    //頁面繪製
    private void drawPage(PdfDocument.Page page,
                          int pagenumber) {
        Canvas canvas = page.getCanvas();
       
        //這裡是頁碼。頁碼不能從0開始
        pagenumber++;

        Paint paint = new Paint();

        PageInfo pageInfo = page.getInfo();

        //draw heart rate line
        if (pageWidth > pageHeight){
            drawViewsOnPaper(Canvas canvas, Paint paint);
        }
    }

    //draw views
    private void drawViewsOnPaper(Canvas canvas, Paint paint){
		//這裡繪製要在列印的紙上的內容,如果精確一點的話,根據pageHeight,pageWidth計算就行,這裡的內容和自定義View的內容一樣,
		//把自定義View繪製的東西拉過來直接就可以用
    }
}
        大概解釋下,這個MyPrintAdapter繼承自PrintDocumentAdapter,PrintDocumentAdapter有自己的生命週期,這個生命週期是和列印時的操作對應上的,如果有需要的引數變數可以通過構造方法傳進去,比如我就傳了個Context,雖然沒怎麼用,他的生命週期依次為onStart,onLayout,onWrite,onFinish,從名字上就能看出來大概什麼時候呼叫,start用在剛開始的時候,layout的回撥是在生成列印預覽的時候佈局時候的回撥,程式碼在寫的過程就需要把所需要的東西繪製上去,這時就走了write的回撥,在write的回撥裡面用自定義view的方式繪製需要的內容就行了,finish就是完事了。這個生命週期比較簡單,可以自行參照原始碼看看就知道大致流程了。WriteResultCallBack這個回撥就是繪製東西的流程,也有自己的週期,可以通過回撥的方式通知PrintDocumentAdapter繪製的相關結果失敗或者成功。其餘部分程式碼上都有解釋。

       這裡面有個比較麻煩的地方就是:android在繪製的時候是按照dpi,或者px來繪製內容的,但是在紙上的話就是按照cm來繪製內容的,這中間有一個比例轉化關係,getHeightMiles獲取到mile值,這個值是比英寸還小的單位,轉化為英寸後,比如轉化為英寸後*72就是72dots per inch,就是72dpi,當然你可以設定的更高,根據印表機的配置試試看吧。

      另外需要注意的是,用手機列印比不上電腦,沒有想象的那麼快,需要等一小會兒。

      3.參考的內容,可以自己檢視sdk下的source原始檔,sdk原始檔檢視方式,比如類的包名是package android.print,直接去source下android資料夾下的print資料夾就能找到。或者官方API,官方API連結:http://www.android-doc.com/reference/android/print/PrintDocumentAdapter.html