Android 截圖(Screenshot)程式碼流程小結
一、基本介紹
在Android 4.0 之前,Android手機上如果要使用截圖功能,只能通過Root手機,且使用第3方截圖軟體來實現截圖功能。
Android4.0中,系統自帶了截圖功能,使用方法是音量下(VOLUME_DOWN)鍵+電源(Power)鍵。
在同時按下2鍵並保持0.5s左右後,會聽到咔嚓一聲響聲,並彈出如下的一個浮動動畫,顯示截圖效果。
二、程式碼呼叫流程
以模組來劃分的話,截圖功能的程式碼會依次呼叫Policy,SystemUI,Surface相關的程式碼,具體流程如下流程圖所示
Policy(PhoneWindowManager.
SystemUI(TakeScreenshotService.java和GlobalScreenshot.java):收到來自Client端的截圖請求後,開始呼叫Surface的API截圖,並將擷取到的圖片通過WindowManager以浮動視窗的形式顯示給使用者檢視。
Surface(Surface.java和android_view_Surface.cpp):Framework層的Surface.java只是提供一個native方法,實際實現在JNI處的android_view_Surface.cpp中的doScreenshot(...)方法。三、App端如何使用截圖功能
以目前程式碼情況看,Surface.java中的screenshot方法是有@hide標記的,即在預設的SDK中是沒有此方法的,暫不支援App端直接使用。
因為只是@hide標記,如果App要使用,當然也是有方法的,但會和手機ROM有依賴性。我所使用的方法是,在Android原始碼環境下進行編譯,為app賦予system的share uid和platform的簽名,然後就可以在4.0的手機中使用App來截圖了。
關鍵步驟:
1) 在AndroidManifest.xml中加入android:sharedUserId="android.uid.system"
2)在Android.mk中加入platform簽名屬性,並在原始碼環境下編譯。或者將相關jar包引入到Eclipse中做第3方庫引用,並將生成的apk重新打上platform簽名
注:
在SurfceFlinger.cpp的onTransact方法中,有對截圖的操作進行許可權認證,所以需要為app使用system的shareUserId。
另:從此處程式碼看,使用android.permission.READ_FRAME_BUFFER的permission應該也可以使用,但測試時通過這種方式未實現,可能哪裡操作不對,也有可能Google還只是預留給後續改進而已。
附錄:App的原始碼檔案:
1. Activity檔案
[java] view plain copy print?- package com.arvinhe.testscreenshot;
- import android.app.Activity;
- import android.content.Context;
- import android.graphics.Bitmap;
- import android.graphics.Canvas;
- import android.graphics.Matrix;
- import android.os.Bundle;
- import android.util.DisplayMetrics;
- import android.view.Display;
- import android.view.Surface;
- import android.view.View;
- import android.view.WindowManager;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.ImageView;
- publicclass TestScreenShotActivity extends Activity implements OnClickListener{
- private ImageView img_display;
- private Button bt_screenshot;
- private Display mDisplay;
- private DisplayMetrics mDisplayMetrics;
- private Matrix mDisplayMatrix;
- private Bitmap mScreenBitmap;
- private WindowManager mWindowManager;
- @Override
- publicvoid onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- bt_screenshot = (Button)findViewById(R.id.bt_screenshot);
- img_display = (ImageView)findViewById(R.id.img_display);
- bt_screenshot.setOnClickListener(this);
- mDisplayMatrix = new Matrix();
- mWindowManager = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
- mDisplay = mWindowManager.getDefaultDisplay();
- mDisplayMetrics = new DisplayMetrics();
- mDisplay.getRealMetrics(mDisplayMetrics);
- }
- @Override
- publicvoid onClick(View v) {
- if(v.equals(bt_screenshot)){
- 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]);
- }
- 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;
- }
- // If we couldn't take the screenshot, notify the user
- if (mScreenBitmap == null) {
- return;
- }
- // Optimizations
- mScreenBitmap.setHasAlpha(false);
- mScreenBitmap.prepareToDraw();
- img_display.setImageBitmap(mScreenBitmap);
- }
- }
- /**
- * @return the current display rotation in degrees
- */
- privatefloat getDegreesForRotation(int value) {
- switch (value) {
- case Surface.ROTATION_90:
- return 360f - 90f;
- case Surface.ROTATION_180:
- return 360f - 180f;
- case Surface.ROTATION_270:
- return 360f - 270f;
- }
- return 0f;
- }
- }
2. AndroidManifest.xml檔案
[html] view plain copy print?- <?xmlversion="1.0"encoding="utf-8"?>
- <manifestxmlns:android="http://schemas.android.com/apk/res/android"
- package="com.arvinhe.testscreenshot"
- android:versionCode="1"
- android:versionName="1.0"
- android:sharedUserId="android.uid.system">
- <uses-sdkandroid:minSdkVersion="15"/>
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name">
- <activity
- android:name=".TestScreenShotActivity"
- android:label="@string/app_name">
- <intent-filter>
- <actionandroid:name="android.intent.action.MAIN"/>
- <categoryandroid:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
- </manifest>
3. Layout檔案
[html] view plain copy print?- <?xmlversion=