截圖原始碼分析(續)
阿新 • • 發佈:2019-02-11
上節談到原始碼截圖需要呼叫類PhoneWindowManager中介面函式takeScreenshot(),下面我們主要分析一下截圖操作是如何實現的:
第一步,進入函式takeScreenshot中,frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
private void takeScreenshot() {
synchronized (mScreenshotLock) {
ComponentName cn = new ComponentName("com.android.systemui",
"com.android.systemui.screenshot.TakeScreenshotService");
Intent intent = new Intent();
intent.setComponent(cn);
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mScreenshotLock) {
Messenger messenger = new Messenger(service);
Message msg = Message.obtain(null, 1);
final ServiceConnection myConn = this;
msg.replyTo = new Messenger(h);
msg.arg1 = msg.arg2 = 0;
if (mStatusBar != null && mStatusBar.isVisibleLw())
msg.arg1 = 1;
if (mNavigationBar != null && mNavigationBar.isVisibleLw())
msg.arg2 = 1;
messenger.send(msg);
}
}
};
if (mContext.bindService(
intent, conn, Context.BIND_AUTO_CREATE, UserHandle.USER_CURRENT)) {
mScreenshotConnection = conn;
mHandler.postDelayed(mScreenshotTimeout, 10000);
}
}
}
首先通過ComponentName獲悉,需要繫結的服務是TakeScreenshotService,Message.obtain(null, 1)可知msg.what設為1;當mStatusBar(狀態列)和mNavigationBar(導航條)不為空,而且isVisibleLw為true時,msg.arg1和msg.arg2分別為1。最後通過bindService啟動服務TakeScreenshotService。
第二步,進入TakeScreenshotService服務
frameworks/base/packages/SystemUI/src/com/android/systemui/TakeScreenshotService.java
public class TakeScreenshotService extends Service {
private static GlobalScreenshot mScreenshot;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
final Messenger callback = msg.replyTo;
if (mScreenshot == null) {
mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
}
mScreenshot.takeScreenshot(new Runnable() {
@Override public void run() {
Message reply = Message.obtain(null, 1);
try {
callback.send(reply);
} catch (RemoteException e) {
}
}
}, msg.arg1 > 0, msg.arg2 > 0);
}
}
};
}
根據第一步的分析獲悉,msg.what應該是1,由mHandler對訊息進行處理,假如第一次啟動服務TakeScreenshotService,則物件mScreenshot為空,則需要建立一個GlobalScreenshot的物件,通過呼叫物件mScreenshot中函式takeScreenshot實現截圖功能。
第三步,進入類GlobalScreenshot
frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ GlobalScreenshot.java
在介紹GlobalScreenshot之前,我們先了解一下類SaveImageInBackgroundData,該類的實現如下,
class SaveImageInBackgroundData {
Context context;
Bitmap image;
Uri imageUri;
Runnable finisher;
int iconSize;
int result;
}
其中,image用來儲存截圖;imageUri儲存截圖存放路徑;finisher處理截圖的一個執行緒;iconSize截圖佔用空間,result截圖結果,0表示截圖成功,1表示截圖失敗。
public GlobalScreenshot(Context context) {
/ Setup the window that we are going to use
mWindowLayoutParams = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
PixelFormat.TRANSLUCENT);
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mNotificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// Get the various target sizes
mNotificationIconSize =
r.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
// Scale has to account for both sides of the bg
mBgPadding = (float) r.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
// Setup the Camera shutter sound mCameraSound = new MediaActionSound(); mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
}
首先通過建構函式GlobalScreenshot(Context context)建立一個物件,mWindowLayoutParams為窗口布局設定引數;mNotificationManager啟動通知欄服務;mNotificationIconSize通知欄圖示大小設定;mBgPadding截圖邊框背景樣式;mBgPaddingScale邊框寬度;mCameraSound截圖聲音設定。
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
// We need to orient the screenshot correctly (and the Surface api seems to take screenshots
// only in the natural orientation of the device :!)
mDisplay.getRealMetrics(mDisplayMetrics);
float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
float degrees = getDegreesForRotation(mDisplay.getRotation());
boolean requiresRotation = (degrees > 0);
if (requiresRotation) {
// Get the dimensions of the device in its native orientation
mDisplayMatrix.reset();
mDisplayMatrix.preRotate(-degrees);
mDisplayMatrix.mapPoints(dims);
dims[0] = Math.abs(dims[0]);
dims[1] = Math.abs(dims[1]);
}
// Take the screenshot
mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
if (requiresRotation) {
// Rotate the screenshot to the current orientation
Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(ss);
c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
c.rotate(degrees);
c.translate(-dims[0] / 2, -dims[1] / 2);
c.drawBitmap(mScreenBitmap, 0, 0, null);
c.setBitmap(null);
mScreenBitmap = ss;
}
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
// Start the post-screenshot animation
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
statusBarVisible, navBarVisible);
}
陣列dims表示螢幕顯示長寬畫素數;degree表示螢幕自動旋轉度數;如果螢幕自動旋轉,則需要進行調整,通過Surface.screenshot((int) dims[0], (int) dims[1])將螢幕截圖,並暫時儲存在變數mScreenBitmap中,緊接著呼叫 startAnimation,startAnimation函式主要實現兩個功能:第一,實現截圖的動畫效果;第二,將所獲取截圖儲存在指定的路徑下。
第四步,下面繼續分析startAnimation函式
private void startAnimation(final Runnable finisher, int w, int h, boolean statusBarVisible,
boolean navBarVisible) {
// Add the view for the animation
mScreenshotView.setImageBitmap(mScreenBitmap);
mScreenshotLayout.requestFocus();
// Setup the animation with the screenshot just taken
if (mScreenshotAnimation != null) {
mScreenshotAnimation.end();
}
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
statusBarVisible, navBarVisible);
mScreenshotAnimation = new AnimatorSet();
mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Save the screenshot once we have a bit of time now
saveScreenshotInWorkerThread(finisher);
mWindowManager.removeView(mScreenshotLayout);
}
});
mScreenshotLayout.post(new Runnable() {
@Override
public void run() {
mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mScreenshotView.buildLayer();
mScreenshotAnimation.start();
}
});
}
函式createScreenshotDropInAnimation和createScreenshotDropOutAnimation分別表示截圖動畫進入和退出。具體實現就不再分析,隨後建立了AnimatorSet物件mScreenshotAnimation,該物件增加一個介面進行監聽,主要實現截圖儲存等操作。由mScreenshotLayout傳送訊息啟動該操作,進入函式saveScreenshotInWorkerThread中。
private void saveScreenshotInWorkerThread(Runnable finisher) {
SaveImageInBackgroundData data = new SaveImageInBackgroundData();
data.context = mContext;
data.image = mScreenBitmap;
data.iconSize = mNotificationIconSize;
data.finisher = finisher;
new SaveImageInBackgroundTask(mContext, data, mNotificationManager,
SCREENSHOT_NOTIFICATION_ID).execute(data);
}
在函式saveScreenshotInWorkerThread中,首先建立一個SaveImageInBackgroundData物件,我們之前已經介紹過,為該物件中的變數進行賦值,並呼叫SaveImageInBackgroundTask建構函式。
frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ GlobalScreenshot.java
SaveImageInBackgroundTask類與GlobalScreenshot在同一個java檔案下
class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Void,
SaveImageInBackgroundData> {
SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data,
NotificationManager nManager, int nId) {
Resources r = context.getResources();
// Prepare all the output metadata
mImageTime = System.currentTimeMillis();
String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date(mImageTime));
String imageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).getAbsolutePath();
mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);
mImageFilePath = String.format(SCREENSHOT_FILE_PATH_TEMPLATE, imageDir,
SCREENSHOTS_DIR_NAME, mImageFileName);
// Create the large notification icon
int imageWidth = data.image.getWidth();
int imageHeight = data.image.getHeight();
int iconSize = data.iconSize;
final int shortSide = imageWidth < imageHeight ? imageWidth : imageHeight;
Bitmap preview = Bitmap.createBitmap(shortSide, shortSide, data.image.getConfig());
Canvas c = new Canvas(preview);
Paint paint = new Paint();
ColorMatrix desat = new ColorMatrix();
desat.setSaturation(0.25f);
paint.setColorFilter(new ColorMatrixColorFilter(desat));
Matrix matrix = new Matrix();
matrix.postTranslate((shortSide - imageWidth) / 2,
(shortSide - imageHeight) / 2);
c.drawBitmap(data.image, matrix, paint);
c.drawColor(0x40FFFFFF);
Bitmap croppedIcon = Bitmap.createScaledBitmap(preview, iconSize, iconSize, true);
// Show the intermediate notification
mTickerAddSpace = !mTickerAddSpace;
mNotificationId = nId;
mNotificationManager = nManager;
mNotificationBuilder = new Notification.Builder(context)
.setTicker(r.getString(R.string.screenshot_saving_ticker)
+ (mTickerAddSpace ? " " : ""))
.setContentTitle(r.getString(R.string.screenshot_saving_title))
.setContentText(r.getString(R.string.screenshot_saving_text))
.setSmallIcon(R.drawable.stat_notify_image)
.setWhen(System.currentTimeMillis());
mNotificationStyle = new Notification.BigPictureStyle()
.bigPicture(preview);
mNotificationBuilder.setStyle(mNotificationStyle);
Notification n = mNotificationBuilder.build();
n.flags |= Notification.FLAG_NO_CLEAR;
mNotificationManager.notify(nId, n);
mNotificationBuilder.setLargeIcon(croppedIcon);
mNotificationStyle.bigLargeIcon(null);
}
imageDate截圖檔名稱;imageDir 截圖檔案儲存路徑;shortSide手機螢幕寬度;preview建立長寬都為shortSide的Bitmap物件;mNotificationBuilder通知欄通知資訊設定;
protected SaveImageInBackgroundData doInBackground(SaveImageInBackgroundData... params) {
if (params.length != 1) return null;
// By default, AsyncTask sets the worker thread to have background thread priority, so bump
// it back up so that we save a little quicker.
Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
Context context = params[0].context;
Bitmap image = params[0].image;
Resources r = context.getResources();
try {
// Save the screenshot to the MediaStore
ContentValues values = new ContentValues();
ContentResolver resolver = context.getContentResolver();
values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath);
values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName);
values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName);
values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime);
values.put(MediaStore.Images.ImageColumns.DATE_ADDED, mImageTime);
values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, mImageTime);
values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png");
/// M: FOR ALPS00266037 & ALPS00289039 pic taken by phone shown wrong on cumputer. @{
values.put(MediaStore.Images.ImageColumns.WIDTH, image.getWidth());
values.put(MediaStore.Images.ImageColumns.HEIGHT, image.getHeight());
/// M: FOR ALPS00266037 & ALPS00289039. @}
Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
OutputStream out = resolver.openOutputStream(uri);
image.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();
out.close();
// update file size in the database
values.clear();
params[0].imageUri = uri;
params[0].result = 0;
} catch (Exception e) {
// IOException/UnsupportedOperationException may be thrown if external storage is not
// mounted
params[0].result = 1;
}
return params[0];
}
由於儲存檔案需要耗時,故該操作在doInBackground函式中進行,即在後臺執行。設定該執行緒的優先順序為THREAD_PRIORITY_FOREGROUND,建立values變數,由ContentResolver呼叫insert將該截圖檔案加入到media資料庫中,以方便在相簿中進行查詢;將截圖檔案所儲存路徑/大小/截圖結果等資訊儲存在params[0]中,並返回給介面進行處理,如果截圖圖片出錯,則params[0].result = 1;
截圖函式takeScreenshot分析到此結束了,其中與底層進行互動的位於第三步Surface.screenshot((int) dims[0], (int) dims[1])。Framework層的Surface.java提供一個native方法,實際實現在JNI處的android_view_Surface.cpp中的nativeScreenshot(...)方法。
該函式主要功能包括以下幾個:
第一,啟動TakeScreenshotService服務
第二,建立GlobalScreenshot物件,由該物件進行手機截圖操作。
第三,將所獲取的截圖加入到media資料庫中,以方便相簿中查詢。