Android手勢密碼原理分析
在上一篇介紹了手勢密碼的使用,這一篇將主要介紹手勢密碼的原理,手勢密碼的功能主要是由自定義PatternLockView實現的。那咱這就一步一步來揭開PatternLockView的面紗。
效果圖
步驟
第一步
自定義PatternLockView繼承View,重寫兩個構造方法,一個在xml中定義會呼叫,一個在java程式碼中建立物件會呼叫。但不管怎麼定義,都會走到這個構造中。
public PatternLockView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes (attrs, R.styleable.PatternLockView);
try {
sDotCount = typedArray.getInt(R.styleable.PatternLockView_dotCount,
DEFAULT_PATTERN_DOT_COUNT);
mPathWidth = (int) typedArray.getDimension(R.styleable.PatternLockView_pathWidth,
ResourceUtils.getDimensionInPx (getContext(), R.dimen.pattern_lock_path_width));
mNormalDotStateColor = typedArray.getColor(R.styleable.PatternLockView_normalDotStateColor,
ResourceUtils.getColor(getContext(), R.color.white));
mCorrectDotStateColor = typedArray.getColor(R.styleable.PatternLockView _correctDotStateColor,
ResourceUtils.getColor(getContext(), R.color.white));
mCorrectDotStrokeColor = typedArray.getColor(R.styleable.PatternLockView_correctDotStrokeStateColor,
ResourceUtils.getColor(getContext(), R.color.white));
mCorrectLineStateColor = typedArray.getColor(R.styleable.PatternLockView_correctLineStateColor,
ResourceUtils.getColor(getContext(), R.color.white));
mWrongLineStateColor = typedArray.getColor(R.styleable.PatternLockView_wrongLineStateColor,
ResourceUtils.getColor(getContext(), R.color.white));
mWrongDotStateColor = typedArray.getColor(R.styleable.PatternLockView_wrongDotStateColor,
ResourceUtils.getColor(getContext(), R.color.white));
mWrongDotStrokeStateColor = typedArray.getColor(R.styleable.PatternLockView_wrongDotStrokeStateColor,
ResourceUtils.getColor(getContext(), R.color.white));
mDotNormalSize = (int) typedArray.getDimension(R.styleable.PatternLockView_dotNormalSize,
ResourceUtils.getDimensionInPx(getContext(), R.dimen.pattern_lock_dot_size));
mDotSelectedSize = (int) typedArray.getDimension(R.styleable.PatternLockView_dotSelectedSize,
ResourceUtils.getDimensionInPx(getContext(), R.dimen.pattern_lock_dot_selected_size));
mDotAnimationDuration = typedArray.getInt(R.styleable.PatternLockView_dotAnimationDuration,
DEFAULT_DOT_ANIMATION_DURATION);
} finally {
typedArray.recycle();
}
//獲取繪製點的個數
mPatternSize = sDotCount * sDotCount;
//存放選中的點
mPattern = new ArrayList<>(mPatternSize);
//二維陣列 選中點時置為true
mPatternDrawLookup = new boolean[sDotCount][sDotCount];
//二維陣列 存放點物件
mDotStates = new DotState[sDotCount][sDotCount];
//通過迴圈的方式,建立點物件,並對點初始化大小
for (int i = 0; i < sDotCount; i++) {
for (int j = 0; j < sDotCount; j++) {
mDotStates[i][j] = new DotState();
mDotStates[i][j].mSize = mDotNormalSize;
}
}
//存放監聽物件
mPatternListeners = new ArrayList<>();
initView();
}
這裡主要拿到我們自定義的點的數量 點的顏色 線的顏色 線的寬度等資訊,建立了存放選中點的集合 存放監聽物件的集合和兩個二維陣列及點物件。
再來看下initView()方法
private void initView() {
//設定View可點選
setClickable(true);
//設定點與點連線畫筆
mPathPaint = new Paint();
mPathPaint.setAntiAlias(true);
mPathPaint.setDither(true);
mPathPaint.setColor(mCorrectLineStateColor);
mPathPaint.setStyle(Paint.Style.STROKE);
//當畫筆樣式為STROKE或FILL_OR_STROKE時,設定筆刷的圖形樣式,如圓形樣式Cap.ROUND,或方形樣式Cap.SQUARE
mPathPaint.setStrokeJoin(Paint.Join.ROUND);
//設定繪製時各圖形的結合方式,如平滑效果等
mPathPaint.setStrokeCap(Paint.Cap.ROUND);
mPathPaint.setStrokeWidth(mPathWidth);
//設定畫點的畫筆
mDotPaint = new Paint();
mDotPaint.setAntiAlias(true);
mDotPaint.setDither(true);
//設定點外環的畫筆
mRingPaint = new Paint();
mRingPaint.setAntiAlias(true);
mRingPaint.setDither(true);
//設定點放大縮小動畫(用於隱藏模式)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !isInEditMode()) {
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.fast_out_slow_in);
mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
getContext(), android.R.interpolator.linear_out_slow_in);
}
}
這裡主要對各個畫筆做了初始化工作,也可以通過Java程式碼進行更改,初始化工作完成後,就要捕捉手勢了。
第二步
通過onTouchEvent()方法獲取手勢座標
@Override
public boolean onTouchEvent(MotionEvent event) {
//判斷View是否可用
if (!mInputEnabled || !isEnabled()) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handleActionDown(event);
return true;
case MotionEvent.ACTION_UP:
handleActionUp(event);
return true;
case MotionEvent.ACTION_MOVE:
handleActionMove(event);
return true;
case MotionEvent.ACTION_CANCEL:
mPatternInProgress = false;
resetPattern();
notifyPatternCleared();
return true;
}
return false;
}
這裡對手指的按下 移動 擡起 取消都做了判斷
取消的操作比較簡單,就是重置各個狀態
下邊分別來看下按下 移動 擡起
handleActionDown按下方法
private void handleActionDown(MotionEvent event) {
//每次按下都重置下狀態
resetPattern();
//獲取手指按下的座標,以View區域的左上頂點為(0,0)座標
float x = event.getX();
float y = event.getY();
//通過detectAndAddHit方法判斷首次按下是否觸碰到點
Dot hitDot = detectAndAddHit(x, y);
//如果觸碰到點,則改變狀態,開始畫選中點顏色和與點的連線
//如果沒有觸碰上,則不畫線(通過mPatternInProgress狀態控制)
if (hitDot != null) {
mPatternInProgress = true;
mPatternViewMode = CORRECT;
notifyPatternStarted();
} else {
mPatternInProgress = false;
notifyPatternCleared();
}
//如果首次觸碰到點,則進行區域性重新整理
if (hitDot != null) {
//獲取選中點的中心座標
float startX = getCenterXForColumn(hitDot.mColumn);
float startY = getCenterYForRow(hitDot.mRow);
float widthOffset = mViewWidth / 2f;
float heightOffset = mViewHeight / 2f;
// 區域性重新整理其實沒多大用,在onDraw裡邊還是所有程式碼都走
invalidate((int) (startX - widthOffset),
(int) (startY - heightOffset),
(int) (startX + widthOffset), (int) (startY + heightOffset));
}
//把首次按下的座標賦值給mInProgressX mInProgressY
//當選中時可開始畫線
mInProgressX = x;
mInProgressY = y;
}
按下操作主要是獲取首次按下座標,並通過座標拿到是否觸碰到某一個點上,如果觸碰到點上,則改變點的顏色並開始畫點與手指的連線。
handleActionMove移動方法
private void handleActionMove(MotionEvent event) {
float x = event.getX();
float y = event.getY();
Dot hitDot = detectAndAddHit(x, y);
int patternSize = mPattern.size();
//手指按下時沒有選中點,當滑動時,滑動到點上進入此判斷語句
//改變mPatternInProgress mPatternViewMode狀態,重畫選中點和外環的顏色及開始畫與點的連線
if (hitDot != null && patternSize == 1) {
mPatternInProgress = true;
mPatternViewMode = CORRECT;
notifyPatternStarted();
}
mInProgressX = event.getX();
mInProgressY = event.getY();
//簡單粗暴 重新繪製
invalidate();
}
移動操作主要是根據不斷移動的座標,判斷是否觸碰到點上,如果觸碰上了就新增到選中點的集合內,並重畫選中點和外環的顏色及開始畫與點的連線。
handleActionUp擡起方法
private void handleActionUp(MotionEvent event) {
// 判斷手勢集合中是否有選中的點
if (!mPattern.isEmpty()) {
//重置狀態 阻止畫最後一個點與手指之間的連線
mPatternInProgress = false;
notifyPatternDetected();
//擡起的時候再繪製一次
invalidate();
}
}
擡起操作主要是判斷存放點的集合是否為空,如果不為空,則重置狀態,阻止畫最後一個點與手指之間的連線。
detectAndAddHit方法
/**
* 根據x,y座標確定是否點選到點上
*/
private Dot detectAndAddHit(float x, float y) {
//通過checkForNewHit判斷座標是否在一個點上
final Dot dot = checkForNewHit(x, y);
//如果在則返回點 如果不在則返回null
if (dot != null) {
Dot fillInGapDot = null;
//拿到初始化定義存放選中點的集合
final ArrayList<Dot> pattern = mPattern;
if (!pattern.isEmpty()) {
//拿到集合中最後一個點
Dot lastDot = pattern.get(pattern.size() - 1);
int dRow = dot.mRow - lastDot.mRow;
int dColumn = dot.mColumn - lastDot.mColumn;
int fillInRow = lastDot.mRow;
int fillInColumn = lastDot.mColumn;
//重新計算行
/**
* 重新計算行
* 例如:從(0,1)直接到(2,1),想跳過(1,1)時,則通過此方法把第一行新增進去
*/
if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) {
fillInRow = lastDot.mRow + ((dRow > 0) ? 1 : -1);
}
//重新計算列
if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) {
fillInColumn = lastDot.mColumn + ((dColumn > 0) ? 1 : -1);
}
//
fillInGapDot = Dot.of(fillInRow, fillInColumn);
}
/**
* 例如:從(0,1)直接到(2,1),想跳過(1,1)時,則通過此方法把第一行新增進去
*/
if (fillInGapDot != null
&& !mPatternDrawLookup[fillInGapDot.mRow][fillInGapDot.mColumn]) {
addCellToPattern(fillInGapDot);
}
/**
* 如果中間跳過一個點則先新增跳過的點
* 如果沒有則新增選中的點
*/
addCellToPattern(dot);
/**
* 判斷是否震動
*/
if (mEnableHapticFeedback) {
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
| HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
}
return dot;
}
return null;
}
detectAndAddHit方法主要是根據座標拿到點,並判斷前一個點與現在點中間是否還有必須經過但還有新增的點,如果有則優先新增中間點,如果沒有則直接新增現在的點。
checkForNewHit方法
/**
* 檢查是否滑動到點上
*/
private Dot checkForNewHit(float x, float y) {
//判斷y座標是否在點上
final int rowHit = getRowHit(y);
if (rowHit < 0) {
return null;
}
//判斷x座標是否在點上
final int columnHit = getColumnHit(x);
if (columnHit < 0) {
return null;
}
/**
* 如果已經選中,則不再選中
*/
if (mPatternDrawLookup[rowHit][columnHit]) {
return null;
}
//返回一個點物件
return Dot.of(rowHit, columnHit);
}
checkForNewHit方法主要是根據x,y座標判斷是否在一個點上,如果是則返回一個點物件。
getRowHit getColumnHit方法
/**
* 根據y座標判斷是否在某一個點的y座標上
* if (y >= hitTop && y <= hitTop + hitSize)這行給點加了一個範圍,在此範圍內都算點上了點
*/
private int getRowHit(float y) {
final float squareHeight = mViewHeight;
float hitSize = squareHeight * mHitFactor;
float offset = getPaddingTop() + (squareHeight - hitSize) / 2f;
for (int i = 0; i < sDotCount; i++) {
float hitTop = offset + squareHeight * i;
if (y >= hitTop && y <= hitTop + hitSize) {
return i;
}
}
return -1;
}
/**
* 根據x座標判斷是否在某一個點的x座標上
* if (x >= hitLeft && x <= hitLeft + hitSize)這行給點加了一個範圍,在此範圍內都算點上了點
*/
private int getColumnHit(float x) {
final float squareWidth = mViewWidth;
float hitSize = squareWidth * mHitFactor;
float offset = getPaddingLeft() + (squareWidth - hitSize) / 2f;
for (int i = 0; i < sDotCount; i++) {
final float hitLeft = offset + squareWidth * i;
if (x >= hitLeft && x <= hitLeft + hitSize) {
return i;
}
}
return -1;
}
這兩個方法是給點加了一個範圍,在此範圍內都算點上了點。
addCellToPattern方法
private void addCellToPattern(Dot newDot) {
//將選中的點置為true
mPatternDrawLookup[newDot.mRow][newDot.mColumn] = true;
//給集合中新增選中的點
mPattern.add(newDot);
//開始點放大縮小動畫(隱藏模式用)
startDotSelectedAnimation(newDot);
//回撥給使用者選中的點
notifyPatternProgress();
}
addCellToPattern方法是將選中的點添到集合中,並把mPatternDrawLookup中點的位置改為true。
以上就是按下 移動 擡起的主要方法,下面再來看下點和線是如何繪製的。
第三步
首先通過onSizeChanged方法來確定patternlockview的大小
onSizeChanged方法
@Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
//設定每一個點佔據的寬度大小
int adjustedWidth = width - getPaddingLeft() - getPaddingRight();
mViewWidth = adjustedWidth / (float) sDotCount;
//設定每一個點佔據的高度度大小
int adjustedHeight = height - getPaddingTop() - getPaddingBottom();
mViewHeight = adjustedHeight / (float) sDotCount;
}
通過onSizeChanged方法,獲取到一個點佔據的寬和高
然後就是onDraw繪製方法了,所有的繪製工作都是由它來完成,同時在滑動過程中,此方法也是在不斷的呼叫。
onDraw方法
@Override
protected void onDraw(Canvas canvas) {
//拿到選中點的集合
ArrayList<Dot> pattern = mPattern;
int patternSize = pattern.size();
//拿到存放點是否選中的二維陣列
boolean[][] drawLookupTable = mPatternDrawLookup;
//拿到路徑物件
Path currentPath = mCurrentPath;
//清除所有的直線和曲線的路徑,但保持內部資料結構,以便更快地重用。
currentPath.rewind();
//判斷是否是隱藏模式
boolean drawPath = !mInStealthMode;
if (drawPath) {
//根據當前模式設定連線線的顏色
mPathPaint.setColor(getCurrentLineStateColor());
//是否繪製最後一個點與手指之間的連線
boolean anyCircles = false;
float lastX = 0f;
float lastY = 0f;
for (int i = 0; i < patternSize; i++) {
//拿到選中的點
Dot dot = pattern.get(i);
//如果點沒有被選中,則不需要跳出迴圈
if (!drawLookupTable[dot.mRow][dot.mColumn]) {
return;
}
anyCircles = true;
//拿到某點的中心座標
float centerX = getCenterXForColumn(dot.mColumn);
float centerY = getCenterYForRow(dot.mRow);
if (i != 0) {
DotState state = mDotStates[dot.mRow][dot.mColumn];
currentPath.rewind();
//當到第二個的時候,則lastX,lastY就是選中的第一個點的中心
currentPath.moveTo(lastX, lastY);
if (state.mLineEndX != Float.MIN_VALUE
&& state.mLineEndY != Float.MIN_VALUE) {//主要用於自動繪製
currentPath.lineTo(state.mLineEndX, state.mLineEndY);
} else {//手動繪製時進入的
currentPath.lineTo(centerX, centerY);
}
//把線連線上
canvas.drawPath(currentPath, mPathPaint);
}
//當是第一個時,先拿到第一個的中心點,然後從第一個開始畫線,後續再把下一個點的中心點賦值
lastX = centerX;
lastY = centerY;
}
//這裡繪製最後一個點和和點連線的線
if (mPatternInProgress && anyCircles) {
currentPath.rewind();
currentPath.moveTo(lastX, lastY);
currentPath.lineTo(mInProgressX, mInProgressY);
//把線連線上
canvas.drawPath(currentPath, mPathPaint);
}
}
//迴圈畫點
for (int i = 0; i < sDotCount; i++) {
float centerY = getCenterYForRow(i);//排
for (int j = 0; j < sDotCount; j++) {
DotState dotState = mDotStates[i][j];
float centerX = getCenterXForColumn(j);
float size = dotState.mSize * dotState.mScale;
float translationY = dotState.mTranslateY;
drawCircle(canvas, (int) centerX, (int) centerY + translationY,
size, drawLookupTable[i][j], dotState.mAlpha);
}
}
}
drawCircle方法
private void drawCircle(Canvas canvas, float centerX, float centerY,
float size, boolean partOfPattern, float alpha) {
//如果隱藏模式則不走
if (partOfPattern && !isInStealthMode()) {
//設定點外環的顏色
mRingPaint.setColor(getCurrentDotStrokeColor(partOfPattern));
//畫點的外環
canvas.drawCircle(centerX, centerY, size, mRingPaint);
}
//畫點
mDotPaint.setColor(getCurrentDotStateColor(partOfPattern));
mDotPaint.setAlpha((int) (alpha * 255));
canvas.drawCircle(centerX, centerY, size / 2, mDotPaint);
}
在onDraw方法中對畫什麼顏色的點,什麼顏色的線都做了判斷。
至此整個繪製過程就算完成了,剩下的一些顏色的判斷,各個狀態的回撥,各個模式都比較簡單,就不再這裡特別說明了。
貢獻
這個庫是從Aritra Roy的PatternLockView獲取並添加了一些改進使其更加靈活,如果您發現bug或想改進它的任何方面,可以自由地用拉請求進行貢獻。
相關推薦
Android手勢密碼原理分析
在上一篇介紹了手勢密碼的使用,這一篇將主要介紹手勢密碼的原理,手勢密碼的功能主要是由自定義PatternLockView實現的。那咱這就一步一步來揭開PatternLockView的面紗。 效果圖 步驟 第一步 自定義PatternLockV
Android P zygote 原理分析之SystemServer的啟動
SystemServer 在android中的核心服務之一,系統中的大多數服務都執行在這個程序,所以當zygote 啟動後第一個啟動的就是SystemServer ,因為SystemServer 的重要性,如果SystemServer啟動失敗或者中間出現異常導致崩潰,都會引起
Android手勢密碼實現方案
一、大致介面介紹: 圖1 圖2 圖3 圖4 圖1:手勢密碼繪製介面 【主要是繪製上方的9個提示圖示和9個宮格密碼圖示】 圖2:設定手勢密碼 【監聽手勢的輸入,TouchEvent的事件處理,獲取輸入的手勢密碼,同時顯示在上方的提示區域】 圖3
android消除鋸齒原理分析
前言在Android中view繪畫是很重要的一點,當view重寫、surfaceView重寫,都會涉及到如何把一個檢視畫的完美、邊角不在毛躁躁,下面會從原來、業務場景、防鋸齒、防鋸齒實現原理。一、鋸齒的原由:1. 業務場景1.Android 畫圓形檢視如:使用者頭像圓形的。2
Android RecyclerView工作原理分析(上)
基本使用 RecyclerView的基本使用並不複雜,只需要提供一個RecyclerView.Apdater的實現用於處理資料集與ItemView的繫結關係,和一個RecyclerView.LayoutManager的實現用於 測量並佈局 ItemView
Android Watchdog機制原理分析
如我們所知,當應用超過一定時間無響應的時候,系統為了不讓應用長時處於不可操作的狀態,會彈出一個“無響應”(ANR)的對話方塊,使用者可以選擇強制關閉,從而關掉這個程序。 ANR機制是針對應用的,對於系統程序來說,如果長時間“無響應”,Android系統設計
Android-ANR總結原理分析
1、概述 ANR即Application Not Responding(應用程式無響應),一般在ANR的時候會彈出一個應用無響應對話方塊,同時會候產生一個日誌檔案trace.txt,位於/data/anr/資料夾下面,trace檔案是Android Davik
Android手勢密碼
首先宣告一下,九宮格佈局是從網上扒了一個大神寫好的,大家在專案中實現的話可以直接把: Drawl,GuestureLockView,Point類直接複製到自己的專案中; 想了解功能的可以仔細看下原始碼中的這三個類,裡面寫的也非常詳細; 使用GuestureLockVi
Android手勢密碼view筆記(一)
前言:不知不覺已經在這座陌生又熟悉的城市呆了一年多了,說不出什麼感覺,可是即使是自己感覺自己沒什麼變化,但是周圍的事物卻不斷的在變,不知道自己選擇的路未來如何,但是當下我還是會努力、努力、再努力的,加油,騷年!~說了一堆廢話,哈哈~! 好了,下面進入今天的主題
android黑科技系列——修改鎖屏密碼和惡意鎖機樣本原理分析
無需 功能 log 輔助 數據庫文件 手勢密碼 安全網 樣式 進制 一、Android中加密算法 上一篇文章已經介紹了Android中系統鎖屏密碼算法原理,這裏在來總結說一下: 第一種:輸入密碼算法 將輸入的明文密碼+設備的salt值,然後操作MD5和SHA1之後在轉
Android 65K問題之Multidex原理分析及NoClassDefFoundError的解決方法
bottom mini ati ... types auto weight right for Android 65K問題相信困惑了不少人,盡管AS的出來能夠通過分dex高速解決65K問題,可是同一時候也easy由於某些代碼沒有打包到MainDex裏
android黑科技系列——分析某直播App的協議加密原理以及調用加密方法進行協議參數構造
輸出結果 防護 返回 不能 定義類 多個 類型 所在 文件中 一、前言 隨著直播技術火爆之後,各家都出了直播app,早期直播app的各種請求協議的參數信息都沒有做任何加密措施,但是慢慢的有人開始利用這個後門開始弄刷粉關註工具,可以讓一個新生的小花旦分分鐘變成網紅。所以介於
Android XListView實現原理講解及分析
就是 指定 不同 true -h -name 修改 一個 部分 XListview是一個非常受歡迎的下拉刷新控件,但是已經停止維護了。之前寫過一篇XListview的使用介紹,用起來非常簡單,這兩天放假無聊,研究了下XListview的實現原理,學到了很多,今天分享給大家。
Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析【轉】
其中 sin 類的定義 reason ava tab eas file 現在 Android系統的運行時庫層代碼是用C++來編寫的,用C++ 來寫代碼最容易出錯的地方就是指針了,一旦使用不當,輕則造成內存泄漏,重則造成系統崩潰。不過系統為我們提供了智能指針,避免出現上述問題
Android官方架構組件:Lifecycle詳解&迪士尼彩樂園網站架設原理分析
ner 觀察者 and 順序 觸發 組件 oncreate mcr save 我們先將重要的這些類挑選出來: LifecycleObserver接口( Lifecycle觀察者):實現該接口的類,通過註解的方式,可以通過被LifecycleOwner類的addObserve
Android官方架構組件:Lifecycle詳解&迪士尼彩樂園定制開發原理分析
npr save this end ons 關於 直接 能夠 封裝 Lifecycle 是一個類,它持有關於組件(如 Activity 或 Fragment)生命周期狀態的信息,並且允許其他對象觀察此狀態。 我們只需要2步: 1、Prestener繼承LifecycleOb
Android官方架構組件:Lifecycle詳解&迪士尼彩樂園平臺搭建原理分析
基類 客服 androi lifecycle 利用 思想 pub 遇到 原理 在過去的谷歌IO大會上,Google官方向我們推出了 Android Architecture Components,其中談到Android組件處理生命周期的問題,向我們介紹了 Handling
Android系統root破解原理分析
上一篇文章 Android adb 原始碼分析 理論基礎 root破解過程的終極目標是替換掉系統中的su程式。但是要想替換掉系統中su程式本身就是需要root許可權的,怎樣在root破解過程中獲得root許可權,成為我們研究的重點了。下面我們先清點一下我們需要破解系統情況,假設需要破
滴滴開源Android外掛化框架VirtualAPK原理分析
概述 Activity 支援 Hook ActivityManagerService Hook Instrumentation 啟動外掛Acti
Android LayoutInflater原理分析,帶你一步步深入瞭解View
有段時間沒寫部落格了,感覺都有些生疏了呢。最近繁忙的工作終於告一段落,又有時間寫文章了,接下來還會繼續堅持每一週篇的節奏。 有不少朋友跟我反應,都希望我可以寫一篇關於View的文章,講一講View的工作原理以及自定義View的方法。沒錯,承諾過的文章我是一定要兌現的,而且在View這個話題上我還