日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

Android手势密码探索

發(fā)布時(shí)間:2024/1/8 Android 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android手势密码探索 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Android 智能手機(jī)在全球市場(chǎng)有著極高的市場(chǎng)占有率,越來(lái)越受到廣大消費(fèi)者的青睞。但 Android 作為開(kāi)源操作系統(tǒng),且很容易可以獲得系統(tǒng) root 權(quán)限,Android 系統(tǒng)的安全問(wèn)題也是用戶和開(kāi)發(fā)者最關(guān)心的問(wèn)題之一。

手勢(shì)密碼作為手機(jī)上方便的一種安全保護(hù)措施,受到了眾多 APP 開(kāi)發(fā)者的青睞,市場(chǎng)上一些金融類(lèi) APP 基本都配有手勢(shì)密碼,如下圖即為手勢(shì)繪制過(guò)程的一個(gè)狀態(tài)。

目前大多數(shù) Android 手機(jī)都具有手勢(shì)鎖屏功能,Android 系統(tǒng)自身是帶了手勢(shì)密碼功能的,不同的 ROM 廠商做了不同的定制。本文通過(guò)Android自身的源碼簡(jiǎn)單介紹手勢(shì)密碼的原理。

Android手勢(shì)相關(guān)類(lèi)

回憶或者嘗試一下用手勢(shì)解鎖 Android 手機(jī)的過(guò)程:首先用戶通過(guò)點(diǎn)擊九宮格的點(diǎn)連接出一條路徑,當(dāng)手指抬起時(shí),會(huì)判斷此次連接的點(diǎn)路徑是否和設(shè)置的相匹配。

在這個(gè)過(guò)程中,涉及到兩個(gè)方面(不考慮設(shè)置手勢(shì)時(shí)的存儲(chǔ)):

  • 手勢(shì)的繪制
  • 手勢(shì)的驗(yàn)證/匹配

針對(duì)這兩個(gè)過(guò)程,通過(guò) AOSP 查找源碼,我們可以發(fā)現(xiàn)兩個(gè)相關(guān)類(lèi):

  • LockPatternView.java:View類(lèi),九宮格手勢(shì)圖形顯示的類(lèi)。
  • LockPatternUtils.java:手勢(shì)轉(zhuǎn)換、匹配工具類(lèi)。

本篇文章通過(guò)分析這兩個(gè)類(lèi)中重要的部分來(lái)說(shuō)明手勢(shì)表示和繪制的原理。

LockPatternView

該類(lèi)是 View 的子類(lèi),其中定義了整個(gè)手勢(shì)繪制區(qū)相關(guān)的 View,比如九宮格的點(diǎn)、繪制的路徑、View 的狀態(tài)模式、以及手勢(shì)監(jiān)聽(tīng)等。類(lèi)中覆寫(xiě)了 View 父類(lèi)的 onDraw 方法,點(diǎn)的選中狀態(tài)、繪制線條都是實(shí)時(shí)繪制的。

九宮格中的每個(gè)「宮」作為靜態(tài)內(nèi)部類(lèi)定義為 Cell,每個(gè) Cell 包括兩個(gè)坐標(biāo),即行(row)和列(column),row 和 column 的范圍均在 [0, 3) 內(nèi)。這樣定義的好處,一是利用矩陣的思想來(lái)表示九宮格,二是可以把「row 3 + column*」作為 Cell 的值,用 0~8 共 9 個(gè)數(shù)字來(lái)表示九宮格。比如,繪制的路徑是 「L」 型,就可以用「03678」來(lái)表示這個(gè)路徑。

public static final class Cell {final int row;final int column;// keep # objects limited to 9private static final Cell[][] sCells = createCells();private static Cell[][] createCells() {Cell[][] res = new Cell[3][3];for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {res[i][j] = new Cell(i, j);}}return res;}/*** @param row The row of the cell.* @param column The column of the cell.*/private Cell(int row, int column) {checkRange(row, column);this.row = row;this.column = column;}public int getRow() {return row;}public int getColumn() {return column;}public static Cell of(int row, int column) {checkRange(row, column);return sCells[row][column];}private static void checkRange(int row, int column) {if (row < 0 || row > 2) {throw new IllegalArgumentException("row must be in range 0-2");}if (column < 0 || column > 2) {throw new IllegalArgumentException("column must be in range 0-2");}}@Overridepublic String toString() {return "(row=" + row + ",clmn=" + column + ")";} }

手勢(shì)繪制過(guò)程中,一般有三種狀態(tài):繪制正確、正在繪制、繪制錯(cuò)誤(實(shí)際開(kāi)發(fā)可以設(shè)置為四種,第四種即鎖定狀態(tài))。

手勢(shì)九宮格用「DisplayMode」表示三種顯示模式:

public enum DisplayMode {/*** The pattern drawn is correct (i.e draw it in a friendly color)*/Correct,/*** Animate the pattern (for demo, and help).*/Animate,/*** The pattern is wrong (i.e draw a foreboding color)*/Wrong}

通過(guò)三種模式,可以更改繪制手勢(shì)過(guò)程中及結(jié)束后手勢(shì)狀態(tài)。比如,更改顏色以表示狀態(tài):讓繪制的過(guò)程中,選中的 Cell 和線條用藍(lán)色表示,繪制錯(cuò)誤時(shí)用紅色表示,繪制正確時(shí)用綠色表示。

手勢(shì)繪制過(guò)程中通過(guò)接口OnPatternListener中的4個(gè)監(jiān)聽(tīng)函數(shù)來(lái)監(jiān)聽(tīng)手勢(shì)開(kāi)始、結(jié)束、清除、添加等操作。接口的定義如下:

public static interface OnPatternListener {/*** A new pattern has begun.*/void onPatternStart();/*** The pattern was cleared.*/void onPatternCleared();/*** The user extended the pattern currently being drawn by one cell.** @param pattern The pattern with newly added cell.*/void onPatternCellAdded(List<Cell> pattern);/*** A pattern was detected from the user.** @param pattern The pattern.*/void onPatternDetected(List<Cell> pattern);}

從方法名和注釋就可以看出每個(gè)方法的含義,在此不再贅述。

接下來(lái)看下,手勢(shì)在繪制手勢(shì)的過(guò)程中,View是如何判斷手指當(dāng)前位置是否選中某個(gè) Cell ,以及是否應(yīng)該把該 Cell 連接入手勢(shì)。這里需要了解幾個(gè)函數(shù):

  • getRowHit ( float y )

    用來(lái)確定手指當(dāng)前坐標(biāo) (x, y) 位于九宮格的第幾

  • getColumnHit (float x )

    用來(lái)確定手指當(dāng)前坐標(biāo) (x, y) 位于九宮格的第幾

  • checkForNewHit (float x, float y)

    private Cell checkForNewHit(float x, float y) {final int rowHit = getRowHit(y);if (rowHit < 0) {return null;}final int columnHit = getColumnHit(x);if (columnHit < 0) {return null;}if (mPatternDrawLookup[rowHit][columnHit]) {return null;}return Cell.of(rowHit, columnHit);}

    函數(shù)代碼很好理解,mPatternDrawLookup 是個(gè)全局變量,同樣采用矩陣的形式,用于標(biāo)記九宮格中哪個(gè) Cell 被連接。從 checkForNewHit 中可以看出,已經(jīng)被連接的 Cell,是不會(huì)再被選中的,這也是目前手勢(shì)密碼普遍的做法。如果你需要實(shí)現(xiàn)“每個(gè)點(diǎn)可以被連接多次”的需求,這部分就需要改動(dòng)了。

  • detectAndAddHit (float x, float y)

    用來(lái)檢測(cè)并判斷手指當(dāng)前坐標(biāo) (x, y) 是否需要添加添加進(jìn)當(dāng)前手勢(shì)中。

    private Cell detectAndAddHit(float x, float y) {final Cell cell = checkForNewHit(x, y);if (cell != null) {// check for gaps in existing patternCell fillInGapCell = null;final ArrayList<Cell> pattern = mPattern;if (!pattern.isEmpty()) {final Cell lastCell = pattern.get(pattern.size() - 1);int dRow = cell.row - lastCell.row;int dColumn = cell.column - lastCell.column;int fillInRow = lastCell.row;int fillInColumn = lastCell.column;if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) {fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1);}if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) {fillInColumn = lastCell.column + ((dColumn > 0) ? 1 : -1);}fillInGapCell = Cell.of(fillInRow, fillInColumn);}if (fillInGapCell != null &&!mPatternDrawLookup[fillInGapCell.row][fillInGapCell.column]) {addCellToPattern(fillInGapCell);}addCellToPattern(cell);if (mEnableHapticFeedback) {performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING| HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);}return cell;}return null;}

    首先通過(guò) checkForNewHit 獲得當(dāng)前位置的的 Cell,計(jì)算當(dāng)前Cell 與手勢(shì)中最后一個(gè) Cell 的行列差值。看其中一段代碼

    if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) {fillInRow = lastCell.row + ((dRow > 0) ? 1 : -1); }if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) {fillInColumn = lastCell.column + ((dColumn > 0) ? 1 : -1); }fillInGapCell = Cell.of(fillInRow, fillInColumn);

    判斷條件是:當(dāng)前 Cell 與手勢(shì)中最后一個(gè) Cell 的或者的絕對(duì)差值為 2,且其的絕對(duì)差值不為1,即兩個(gè) Cell 不相鄰(包括水平、豎直、45°方向的相鄰),獲得當(dāng)前 Cell 與手勢(shì)中最后一個(gè) Cell 之間的 Cell,如果該 Cell 沒(méi)有被添加進(jìn)去過(guò),則添加進(jìn)手勢(shì)。

    意思就是說(shuō),繪制的手勢(shì)不會(huì)跨過(guò)沒(méi)有添加的點(diǎn)。


前面說(shuō)到,繪制過(guò)程中選中的點(diǎn)和未選中的點(diǎn)是通過(guò)覆寫(xiě) View 的 onDraw 方法實(shí)時(shí)繪制的。onDraw代碼如下:

@Overrideprotected void onDraw(Canvas canvas) {final ArrayList<Cell> pattern = mPattern;final int count = pattern.size();final boolean[][] drawLookup = mPatternDrawLookup;if (mPatternDisplayMode == DisplayMode.Animate) {// figure out which circles to draw// + 1 so we pause on complete patternfinal int oneCycle = (count + 1) * MILLIS_PER_CIRCLE_ANIMATING;final int spotInCycle = (int) (SystemClock.elapsedRealtime() -mAnimatingPeriodStart) % oneCycle;final int numCircles = spotInCycle / MILLIS_PER_CIRCLE_ANIMATING;clearPatternDrawLookup();for (int i = 0; i < numCircles; i++) {final Cell cell = pattern.get(i);drawLookup[cell.getRow()][cell.getColumn()] = true;}// figure out in progress portion of ghosting linefinal boolean needToUpdateInProgressPoint = numCircles > 0&& numCircles < count;if (needToUpdateInProgressPoint) {final float percentageOfNextCircle =((float) (spotInCycle % MILLIS_PER_CIRCLE_ANIMATING)) /MILLIS_PER_CIRCLE_ANIMATING;final Cell currentCell = pattern.get(numCircles - 1);final float centerX = getCenterXForColumn(currentCell.column);final float centerY = getCenterYForRow(currentCell.row);final Cell nextCell = pattern.get(numCircles);final float dx = percentageOfNextCircle *(getCenterXForColumn(nextCell.column) - centerX);final float dy = percentageOfNextCircle *(getCenterYForRow(nextCell.row) - centerY);mInProgressX = centerX + dx;mInProgressY = centerY + dy;}// TODO: Infinite loop here...invalidate();}final Path currentPath = mCurrentPath;currentPath.rewind();// draw the circlesfor (int i = 0; i < 3; i++) {float centerY = getCenterYForRow(i);for (int j = 0; j < 3; j++) {CellState cellState = mCellStates[i][j];float centerX = getCenterXForColumn(j);float translationY = cellState.translationY;if (isHardwareAccelerated() && cellState.hwAnimating) {DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;displayListCanvas.drawCircle(cellState.hwCenterX, cellState.hwCenterY,cellState.hwRadius, cellState.hwPaint);} else {drawCircle(canvas, (int) centerX, (int) centerY + translationY,cellState.radius, drawLookup[i][j], cellState.alpha);}}}// TODO: the path should be created and cached every time we hit-detect a cell// only the last segment of the path should be computed here// draw the path of the pattern (unless we are in stealth mode)final boolean drawPath = !mInStealthMode;if (drawPath) {mPathPaint.setColor(getCurrentColor(true /* partOfPattern */));boolean anyCircles = false;float lastX = 0f;float lastY = 0f;for (int i = 0; i < count; i++) {Cell cell = pattern.get(i);// only draw the part of the pattern stored in// the lookup table (this is only different in the case// of animation).if (!drawLookup[cell.row][cell.column]) {break;}anyCircles = true;float centerX = getCenterXForColumn(cell.column);float centerY = getCenterYForRow(cell.row);if (i != 0) {CellState state = mCellStates[cell.row][cell.column];currentPath.rewind();currentPath.moveTo(lastX, lastY);if (state.lineEndX != Float.MIN_VALUE && state.lineEndY != Float.MIN_VALUE) {currentPath.lineTo(state.lineEndX, state.lineEndY);} else {currentPath.lineTo(centerX, centerY);}canvas.drawPath(currentPath, mPathPaint);}lastX = centerX;lastY = centerY;}// draw last in progress sectionif ((mPatternInProgress || mPatternDisplayMode == DisplayMode.Animate)&& anyCircles) {currentPath.rewind();currentPath.moveTo(lastX, lastY);currentPath.lineTo(mInProgressX, mInProgressY);mPathPaint.setAlpha((int) (calculateLastSegmentAlpha(mInProgressX, mInProgressY, lastX, lastY) * 255f));canvas.drawPath(currentPath, mPathPaint);}}}

這部分代碼比較長(zhǎng),這里就不細(xì)細(xì)分析了,主要流程就是:

  • 判斷當(dāng)前顯示模式是否是正在繪制。如果是,保存連接的點(diǎn)的狀態(tài),計(jì)算手指當(dāng)前所在的點(diǎn)坐標(biāo);如果不是,進(jìn)入第2步。

  • 根據(jù)1中保存的狀態(tài),繪制選中的點(diǎn),已更改選中的點(diǎn)的樣式。

    選中的點(diǎn)和未選中的點(diǎn)的狀態(tài)都是在這部分實(shí)時(shí)完成的,通過(guò)遍歷9個(gè)點(diǎn),根據(jù)1中保存的狀態(tài)改變畫(huà)筆屬性繪制不同的樣式。

  • 繪制連接線(path)。主要是獲得路徑,然后drawPath。

  • 最后就是onTouchEvent處理手指ACTION事件,包括ACTION_DOWN、ACTION_UP、ACTION_MOVE、ACTION_CANCEL事件。每種事件,判斷手勢(shì)繪制是否結(jié)束、改變顯示模式、刷新View、回調(diào)方法。

    LockPatternUtils

    LockPatternUtils是處理手勢(shì)的工具類(lèi),主要看下兩個(gè)方法patternToString、patternToHash兩個(gè)方法。

    • patternToString
    /*** Serialize a pattern.* @param pattern The pattern.* @return The pattern in string form.*/public static String patternToString(List<LockPatternView.Cell> pattern) {if (pattern == null) {return "";}final int patternSize = pattern.size();byte[] res = new byte[patternSize];for (int i = 0; i < patternSize; i++) {LockPatternView.Cell cell = pattern.get(i);res[i] = (byte) (cell.getRow() * 3 + cell.getColumn());}return new String(res);}

    從方法定義可以看到,將手勢(shì)用0~8數(shù)字,轉(zhuǎn)換成byte數(shù)組來(lái)表示。

    • patternToHash
    /** Generate an SHA-1 hash for the pattern. Not the most secure, but it is* at least a second level of protection. First level is that the file* is in a location only readable by the system process.* @param pattern the gesture pattern.* @return the hash of the pattern in a byte array.*/public static byte[] patternToHash(List<LockPatternView.Cell> pattern) {if (pattern == null) {return null;}final int patternSize = pattern.size();byte[] res = new byte[patternSize];for (int i = 0; i < patternSize; i++) {LockPatternView.Cell cell = pattern.get(i);res[i] = (byte) (cell.getRow() * 3 + cell.getColumn());}try {MessageDigest md = MessageDigest.getInstance("SHA-1");byte[] hash = md.digest(res);return hash;} catch (NoSuchAlgorithmException nsa) {return res;}}

    patternToHash的作用是,在patternToString的基礎(chǔ)上,采用「SHA-1」算法對(duì)byte數(shù)組進(jìn)行hash散列。

    值得一提的是,SHA-1雖然不可逆,但算法并不安全。如果采用暴力破解的方式,自己寫(xiě)個(gè)程序很快就能撞對(duì)。

    也許Android的開(kāi)發(fā)者也明白,Android作為開(kāi)源系統(tǒng),無(wú)法做到真正意義上的絕對(duì)安全,除了每個(gè)人都能獲得源碼外,獲得系統(tǒng)root權(quán)限就能拿到系統(tǒng)所有數(shù)據(jù),因此并沒(méi)有花較大的力氣來(lái)處理手勢(shì)的安全問(wèn)題。當(dāng)然,這也是作者的猜想。

    實(shí)際開(kāi)發(fā)中,需要根據(jù)APP及手勢(shì)需求的加密等級(jí),對(duì)手勢(shì)信息進(jìn)行不同程度的加密。如果需要存儲(chǔ)到本地,還涉及到數(shù)據(jù)的本地存儲(chǔ)安全。


    通過(guò)上面的簡(jiǎn)單介紹,相信大家大致了解了手勢(shì)密碼的原理,上面分析的內(nèi)容主要是用戶可以修改的,即如果你需要自定義不同的手勢(shì)樣式,可以更改上面分析的對(duì)應(yīng)部分。

    我個(gè)人基于Android自己的LockPatternView進(jìn)行了簡(jiǎn)單的修改,繪制的樣式如文章開(kāi)始的圖所示,修改的地方如要是drawCircle、圖層、畫(huà)筆。相關(guān)代碼可到y(tǒng)oungmeng/LockPatternView查看。

    總結(jié)

    以上是生活随笔為你收集整理的Android手势密码探索的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。