很多圖文教程和視頻教程都喜歡啰嗦一大堆,才總結(jié)性地說一句:廢話不多說劲妙,下面開始湃鹊。我跟那些妖艷賤貨肯定是要不一樣的。開擼镣奋。
1. 集成zxing
打開項目build.gradle币呵,加入以下代碼:
// zxing 掃描生成二維碼和條形碼
compile 'com.google.zxing:core:3.3.0'
我們這里之所以只集成了zxing的核心包,核心包里面是解析/生成 二維碼/條形碼等一系列算法,并且余赢,包足夠小芯义。
2. 集成CameraView
點擊Google出品的CameraView到項目主頁,直接下載或者clone到本地妻柒,然后把里面的Library文件夾下面的內(nèi)容拷貝到自己的項目工程扛拨。
項目配置完畢。
3. 工作思路和流程
思路很簡單:拍照得到照片举塔,然后把照片交給zxing去解析绑警。我們只需要解析結(jié)果。
先上解析代碼:
/**
* 掃描圖片
*
* @param bitmap 圖片
* @return zxing掃描初始結(jié)果
*/
private Result scanningImage(Bitmap bitmap) {
Map<DecodeHintType, String> hints1 = new Hashtable<>();
hints1.put(DecodeHintType.CHARACTER_SET, "utf-8");
RGBLuminanceSource source = new RGBLuminanceSource(bitmap);
BinaryBitmap bitmap1 = new BinaryBitmap(new HybridBinarizer(source));
QRCodeReader reader = new QRCodeReader();
Result result;
try {
result = reader.decode(bitmap1, hints1);
scanResult(result.getText());
return result;
} catch (NotFoundException | ChecksumException | FormatException e) {
// 解析照片上面的二維碼失敗后央渣,拍照重新解析
if (mCameraView != null && mCameraView.isCameraOpened()) {
mCameraView.takePicture();
}
e.printStackTrace();
}
return null;
}
不過這里就遇到難題了计盒,因為CameraView默認拍攝的是攝像頭所支持的,當(dāng)前寬高比最高的照片芽丹。以目前我正在使用的魅族Pr5為例北启,2100像素拍出來的照片,隨隨便便都是8M以上拔第。這么大的照片暖庄,即使不會OOM,解析速度也會慢的令人發(fā)指楼肪。
事實上,在我的手機(魅族Pro5)上面測試惹悄,OOM倒是不會春叫,但結(jié)果是虛擬機開始頻繁GC,然后應(yīng)用卡死泣港。(其實這就是OOM)
所以暂殖,這個時候就要想到去壓縮圖片,使用到BitmapFactory
的一系列方法当纱,對其進行壓縮呛每。因為CameraView得到的照片是一個byte數(shù)組,我們直接對這個數(shù)組進行生成Bitmap坡氯,然后再根據(jù)其寬高(因為每臺手機攝像頭都不一樣晨横,拍出來的照片也是千差萬別)計算壓縮比,進行壓縮箫柳,最后再將壓縮后的圖片傳給zxing進行解析手形。
流程圖:
graph TD
AA[開始] --> A[拍照]
A --> B[壓縮圖片]
B --> C[發(fā)送圖片給zxing]
C --> D[解析圖片上的二維碼]
D --> F{Success or failed}
F --> |Failed| A
F --> |Success| G[Finish]
(圖有點抽象,將就著看)
在這個流程之中悯恍,比較耗時的地方一般是解析圖片库糠。zxing解析圖片的過程非常的慢,使用我的Pro5進行解析,壓縮400ms以內(nèi)夕春,解析700ms以內(nèi)婉商。使用比較低端的測試機站蝠,壓縮用了500ms(這方面耗時并不多苗沧,算法使用的Google的)繁扎,但是解析卻達到了4000ms——5000ms之間伪节。
目前懷疑這個耗時是因為使用了一個將Bitmap轉(zhuǎn)化為zxing所需的Bitmap的問題吼砂。zxing所需的Bitmap和我們平常使用的Bitmap格式不同顷帖,這一塊也沒有去看和仔細優(yōu)化美旧,應(yīng)該還可以提速一部分。
目前項目只能說可以使用贬墩。比較要注意的是讓相機開始拍照的時機問題榴嗅,因為掃描二維碼是打開相機后自動掃描,不可能讓用戶點擊拍照按鈕再去掃描陶舞。我采用的方式是打開相機頁面之后嗽测,在頁面上一個元素中加上postDelay,1000ms后開始獲取照片肿孵。不過這個也需要加一個判斷:CameraView.isCameraOpen()
否則會出現(xiàn)打開頁面立馬退出唠粥,倒計時完成后依然會視圖去拍照,但是CameraView此時已經(jīng)被銷毀停做,強行使用被銷毀的對象晤愧,自然就會導(dǎo)致應(yīng)用崩潰了。
整個工程完全可以基于CameraView項目中原有的代碼開始使用蛉腌,只需要把代碼拷貝過去就行了官份。我這邊也只是集成在了項目之中,目前自己測試沒什么問題烙丛,但要是真在實際環(huán)境中使用的話舅巷,還需要大量的測試。等基本完善之后河咽,才會考慮將代碼開源出來钠右。
其實也并沒有什么開源的地方,代碼基本上都是各種copy過來的忘蟹,只是其中有很多地方需要一點一點去看飒房,去做優(yōu)化。
還有寒瓦,自定義掃描二維碼界面我就不說了情屹,繪制一個View的事情,不在本教程范圍內(nèi)杂腰。
目前基本就這些垃你,謝謝大家。
下面是代碼部分:
壓縮圖片
/**
* 以字節(jié)的格式壓縮位圖
*
* @param bytes 拍照所得字節(jié)位圖
* @return 壓縮后的Bitmap
*/
private Bitmap byteForCompressBitmap(byte[] bytes) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
options.inSampleSize = computeSampleSize(options, -1, 1920 * 1080);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
}
/**
* 計算圖片樣本大小 for google
*
* @param options BitmapOption 記得inJustBounds設(shè)置為true
* @param minSideLength 最小邊長 一般為 -1
* @param maxNumOfPixels 最大像素數(shù)
* @return 返回可縮放倍數(shù)
*/
public int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
int roundedSize;
if (initialSize <= 8) {
roundedSize = 1;
while (roundedSize < initialSize) {
roundedSize <<= 1;
}
} else {
roundedSize = (initialSize + 7) / 8 * 8;
}
return roundedSize;
}
/**
* 計算圖片初始樣本大小
*
* @param options 開關(guān)
* @param minSideLength 最小邊長
* @param maxNumOfPixels 最大像素數(shù)目
* @return 傳入圖片初始樣本大小
*/
private int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
return lowerBound;
}
if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
return 1;
} else if (minSideLength == -1) {
return lowerBound;
} else {
return upperBound;
}
}
上方zxing解析圖片中的二維碼缺失的RGBLuminanceSource類,如果直接使用zxing包中的RGBLuminanceSource類惜颇,因為接收的圖片格式不同皆刺,會導(dǎo)致不能運行。
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.google.zxing.LuminanceSource;
import java.io.FileNotFoundException;
/**
* This class is used to help decode images from files which arrive as RGB data
* from Android bitmaps. It does not support cropping or rotation.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class RGBLuminanceSource extends LuminanceSource {
private final byte[] luminances;
public RGBLuminanceSource(String path) throws FileNotFoundException {
this(loadBitmap(path));
}
public RGBLuminanceSource(Bitmap bitmap) {
super(bitmap.getWidth(), bitmap.getHeight());
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
// In order to measure pure decoding speed, we convert the entire image
// to a greyscale array
// up front, which is the same as the Y channel of the
// YUVLuminanceSource in the real app.
luminances = new byte[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
int pixel = pixels[offset + x];
int r = (pixel >> 16) & 0xff;
int g = (pixel >> 8) & 0xff;
int b = pixel & 0xff;
if (r == g && g == b) {
// Image is already greyscale, so pick any channel.
luminances[offset + x] = (byte) r;
} else {
// Calculate luminance cheaply, favoring green.
luminances[offset + x] = (byte) ((r + g + g + b) >> 2);
}
}
}
}
private static Bitmap loadBitmap(String path) throws FileNotFoundException {
Bitmap bitmap = BitmapFactory.decodeFile(path);
if (bitmap == null) {
throw new FileNotFoundException("Couldn't open " + path);
}
return bitmap;
}
@Override
public byte[] getRow(int y, byte[] row) {
if (y < 0 || y >= getHeight()) {
throw new IllegalArgumentException(
"Requested row is outside the image: " + y);
}
int width = getWidth();
if (row == null || row.length < width) {
row = new byte[width];
}
System.arraycopy(luminances, y * width, row, 0, width);
return row;
}
// Since this class does not support cropping, the underlying byte array
// already contains
// exactly what the caller is asking for, so give it to them without a copy.
@Override
public byte[] getMatrix() {
return luminances;
}
}