Android錄屏應用開發研究

來源:互聯網
上載者:User

標籤:寫入   osi   結果   pixel   表徵圖   方便   指定   cat   檔案目錄   

1截屏介面
  在Android5.0之前如果希望螢幕,是需要擷取系統root許可權的。但在Android5.0之後Android開放了新的介面android.media.projection,開發人員使用該介面,第三方應用程式無需再擷取系統root許可權也可以直接進行螢幕操作了。查詢其官方api可知,該介面主要用來“螢幕”操作和“音頻錄製”操作,這裡只討論用於螢幕的功能。由於使用了媒體的映射技術手段,故截取的螢幕並不是真正的裝置螢幕,而是截取的通過映射出來的“虛擬螢幕”。不過,因為我們希望的得到的肯定是一張圖而已,而“映射”出來的圖片與系統螢幕完全一致,所以,對於普通截屏操作,該方法完全可行。
  下面是該介面的使用方法:
(1)首先用參數MEDIA_PROJECTION_SERVICE調用Context.getSystemService(),得到MediaProjectionManager類別執行個體。

mMediaProjectionManager=(MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);

(2)其次,調用 createScreenCaptureIntent()得到一個Intent;再次,使用startActivityForResult()啟動螢幕捕捉,在onActivityResult()中擷取resultCode和resultData,以方便下面getMediaProjection()使用。

startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);

(3)將結果返回到getMediaProjection()上,擷取捕捉資料。

mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);

(4)使用mMediaRecorder執行個體通過getSurface()方法擷取螢幕表層,使用上一步中的MediaProjection臨時執行個體通過createVirtualDisplay()方法進行虛擬螢幕的顯示。 

return mMediaProjection.createVirtualDisplay("MainActivity", mScreenWidth,                        mScreenHeight, mScreenDensity,                        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,                        mMediaRecorder.getSurface(), null /* Callbacks */, null /* Handler */);

2懸浮視窗
  首先是一個小的懸浮窗顯示在螢幕右邊介面,點擊一下小懸浮窗,就會彈出一個大的懸浮窗,有相關按鈕進行操作。
  先談一下基本的實現原理,實現這種案頭懸浮窗的效果同Widget比較類似,但是它比Widget要靈活的多。主要是通過WindowManager這個類來實現的,調用這個類的addView方法用於添加一個懸浮窗,updateViewLayout方法用於更新懸浮窗的參數,removeView用於移除懸浮窗。
  WindowManager.LayoutParams這個類用於提供懸浮窗所需的參數,其中有幾個經常會用到的變數:
1)type值用於確定懸浮窗的類型,一般設為TYPE_PHONE(2002),表示在所有應用程式之上,但在狀態列之下。
2)flags值用於確定懸浮窗的行為,比如說不可聚焦,非模態對話方塊等等,屬性非常多。
3)gravity值用於確定懸浮窗的對齊,一般會設為左上方對齊,這樣當拖動懸浮窗的時候方便計算座標。
4)x值用於確定懸浮窗的位置,如果要橫向移動懸浮窗,就需要改變這個值。
5)y值用於確定懸浮窗的位置,如果要縱向移動懸浮窗,就需要改變這個值。
6)width值用於指定懸浮窗的寬度。
7)height值用於指定懸浮窗的高度。
  建立懸浮窗這種表單需要向使用者申請許可權才可以的,因此還需要在AndroidManifest.xml中加入<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />,這在api23之前可以直接在Manifest.xml中加入許可權,但在android6.0之後,需要使用者在系統應用許可權管理中開啟許可權。
  建立浮窗的方法:
(1)首先建立一個名為float_window_small.xml的布局檔案,用於作為小懸浮窗的布局,再建立一個名為float_window_big.xml的布局檔案,用於作為大懸浮窗的布局。
(2)建立一個名為FloatWindowService的類,這個類繼承自Service。
 


(3)如所示在FloatWindowService的onStartCommand方法中開啟了一個定時器Timer,每隔500毫秒(0.5秒)就執行一次RefreshTask()。RefreshTask繼承TimerTask,在RefreshTask當中,要進行判斷,如果手機當前是在案頭的話,就應該顯示懸浮窗,如果手機返回了開啟浮窗的應用程式,就應該移除懸浮窗。而當FloatWindowService被銷毀的時候,應該將定時器停止,否則它還會一直運行。
  建立和移除懸浮窗,都是由MyWindowManager這個類來管理的,比起直接把這些代碼寫在Activity或Service當中,使用一個專門的工具類來管理要好的多。不過要想建立懸浮窗,還是先要把懸浮窗的View寫出來。建立一個名叫FloatWindowSmallView的類,繼承自LinearLayout。建立一個名叫FloatWindowBigView的類,也繼承自LinearLayout。
(4)在FloatWindowSmallView中,通過onTouchEvent(MotionEvent event)判斷手指在螢幕的操作:
1)ACTION_DOWN:記錄手指按下時必要的資料,縱座標的值都需要減去狀態列高度。
2)ACTION_MOVE:手指移動的時候使用updateViewPosition()方法更新小懸浮窗的位置。
3)ACTION_UP:如果手指離開螢幕時,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,則視為觸發了單擊事件,通過openBigWindow()開啟大浮窗。
(5)在FloatWindowBigView中,設定2個按鈕startrecord和stoprecord,對startrecord設定監聽:
1.startrecord:移除大懸浮窗,建立小懸浮窗,開啟ScreenrecordService。
2.stoprecord:移除大懸浮窗,建立小懸浮窗,關閉ScreenrecordService。
3.利用onTouchEvent(MotionEvent event)的case MotionEvent.ACTION_DOWN,移除大懸浮窗,建立小懸浮窗。
3視頻錄製
  在ScreenrecordService中結合截屏完成視頻的錄製,ScreenrecordService繼承Service。為了增加對錄製音視頻的支援,Android系統提供了一個MediaRecorder的類。該類的使用也非常簡單,下面讓我們來瞭解一下這個類。


  與MediaPlayer類非常相似MediaRecorder也有它自己的狀態圖。下面是關於MediaRecorder的各個狀態的介紹:
1)Initial:初始狀態,當使用new()方法建立一個MediaRecorder對象或者調用了reset()方法時,該MediaRecorder對象處於Initial狀態。在設定視頻源或者音頻源之後將轉換為Initialized狀態。另外,在除Released狀態外的其它狀態通過調用reset()方法都可以使MediaRecorder進入該狀態。
2)Initialized:已初始化狀態,可以通過在Initial狀態調用setAudioSource()或setVideoSource()方法進入該狀態。在這個狀態可以通過setOutputFormat()方法設定輸出格式,此時MediaRecorder轉換為DataSourceConfigured狀態。另外,通過reset()方法進入Initial狀態。
3)DataSourceConfigured:資料來源配置狀態,這期間可以設定編碼方式、輸出檔案、旋轉螢幕、預覽顯示等等。可以在Initialized狀態通過setOutputFormat()方法進入該狀態。另外,可以通過reset()方法回到Initial狀態,或者通過prepare()方法到達Prepared狀態。
4)Prepared:就緒狀態,在DataSourceConfigured狀態通過prepare()方法進入該狀態。在這個狀態可以通過start()進入錄製狀態。另外,可以通過reset()方法回到Initialized狀態。
5)Recording:錄製狀態,可以在Prepared狀態通過調用start()方法進入該狀態。另外,它可以通過stop()方法或reset()方法回到Initial狀態。
6)Released:釋放狀態(官方文檔給出的詞叫做Idle state 空閑狀態),可以通過在Initial狀態調用release()方法來進入這個狀態,這時將會釋放所有和MediaRecorder對象綁定的資源。
7)Error:錯誤狀態,當錯誤發生的時候進入這個狀態,它可以通過reset()方法進入Initial狀態。
  下面介紹本應用關於視頻錄取的步驟:
1)首先擷取像素,通過getDisplayMetrics()擷取metrics,得到手機螢幕的解析度。

        DisplayMetrics metrics = new DisplayMetrics();        metrics = getResources().getDisplayMetrics();// service中擷取displaymetrics        mScreenDensity = metrics.densityDpi;        mScreenWidth = metrics.widthPixels;        mScreenHeight = metrics.heightPixels;

2)由於我們需要將錄製的視頻儲存到手機記憶體中,因此將儲存路徑設定到手機的公用儲存視頻的檔案夾下,擷取路徑如下所示:

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)。

3)獲得MediaRecorder的執行個體,初始化MediaRecorder:
設定用於錄製的視頻來源setVideoSource(MediaRecorder.VideoSource.SURFACE);
設定聲音來源:setAudioSource(MediaRecorder.AudioSource.MIC);
設定所錄製的音視頻檔案的格式:setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
設定所錄製視頻的編碼格式:setVideoEncoder(MediaRecorder.VideoEncoder.H264);
設定所錄製的聲音的編碼格式:setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
設定所錄製的聲音的編碼位率:setVideoEncodingBitRate(512 * 1000);
設定錄製視頻的捕獲畫面播放速率:setVideoFrameRate(40);
設定要拍攝的寬度和視頻的高度:setVideoSize(mScreenWidth, mScreenHeight);
設定錄製視頻的儲存路徑:setOutputFile(mVecordFile.getAbsolutePath());
4)調用MediaRecorder的prepare()方法,完成錄製視頻的準備工作。
5)在ScreenrecordService中的onStartCommand(Intent intent, int flags, int startId),調用startrecord()方法。
(1)從ShotApplication共用類中擷取resultCode、data和MediaProjectionManager執行個體。
(2)通過下面mMediaRecorder.getSurface()的串聯,我們把mMediaProjection的輸出內容放到了Surface裡面,而Surface正是MediaRecorder的輸入源,這樣就完成了對mMediaProjection輸出內容的編碼,也就是螢幕採集資料的編碼mVirtualDisplay。

return mMediaProjection.createVirtualDisplay("MainActivity", mScreenWidth,                        mScreenHeight, mScreenDensity,                        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,                        mMediaRecorder.getSurface(), null /* Callbacks */, null /* Handler */);

(3)調用MediaRecorder的start()方法,開始進行錄製。
6)在ScreenrecordService的onDestroy()方法中:
(1)調用MediaRecorder的stop()方法和reset()方法,完成錄製

mMediaRecorder.stop();mMediaRecorder.reset();

(2)對mVirtualDisplay進行釋放。

       if (mVirtualDisplay == null) {            return;        }        mVirtualDisplay.release();

(3)停止截屏,共置空。
 

        if (mMediaProjection != null) {            mMediaProjection.stop();            mMediaProjection = null;        }

(4)發送廣播,通知系統媒體庫更新。

sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.parse("file://"+sampleDir.getAbsolutePath().toString())));

4許可權擷取
  在android 6.0之前,應用的許可權在AndroidManifest.xml靜態申請就可以,但出了android 6.0之後,新的許可權擷取方式除了要求像之前版本一樣在AndroidManifest檔案中靜態申請之外,應用還需根據需要請求許可權,方式採用向使用者顯示一個請求許可權的對話方塊。這些被動態申請的許可權可以在系統設定中被手動關閉。另外,對於類別為NORMAL的許可權,仍然只需要在AndroidManifest檔案中靜態申請,系統安裝時會直接擷取。
  在系統授權彈窗環節,提醒框會有個不再提示的複選框,如果使用者點擊不太提示,並拒絕授權,那麼再下次授權的時候,系統授權彈窗的提示框就不會在提示,所以我們很有必要需要自訂許可權彈窗提示框,那麼流程圖就變成如下了。
 

錄屏應用需要動態擷取的許可權如下:
(1)<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
(2)<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
(3)<uses-permission android:name="android.permission.RECORD_AUDIO"/>
  以上分別是開啟懸浮視窗、寫入SDCARD儲存和開啟麥克風的許可權。上述三種許可權分為2種類型:
<1>系統許可權:
  其中寫入SDCARD儲存和開啟麥克風的許可權,為系統許可權的dangerous類型,需要動態申請。處理許可權申請:
1)使用方法checkSelfPermission()判定是否有許可權。
2)如果沒有許可權,彈出dialog給使用者選擇:requestPermission(),第二個參數code與onRequestPermissionResult()方法中的code對應。
3)判斷使用者是否確認了許可權onRequestPermissionResult ()。
4)在彈出許可權選擇的對話方塊前給使用者show一個dialog,用於引導使用者進行選擇。
  由於本應用需申請2個許可權,因此不能按照上述申請單個許可權的方法進行申請。一次性處理多個許可權申請:
1)在requestPermissions()方法中,定義一個列表permissionsNeeded,通過方法addPermission()將需要申請的許可權加入到列表中。
2)通過for迴圈彈出申請對話方塊,供使用者選擇申請許可權是否應許。
3)如果所有許可權被授權,依然回調onRequestPermissionsResult,檢驗申請的許可權是否被授權。
4)在MainActivity的onCreate中使用if (Build.VERSION.SDK_INT >= 23) 判斷程式進行許可權授權,即運行requestPermissions()方法。
<2>特殊許可權授權
  開啟懸浮視窗許可權是特殊許可權授權,除此還有一個特殊許可權授權(android.permission.WRITE_SETTINGS),對於這兩種特殊許可權,處理如下:
如果是針對 “android.permission.SYSTEM_ALERT_WINDOW”和
“android.permission.WRITE_SETTINGS”這兩個許可權,如果是運行在6.0的版本上是需要走新的許可權模型,如果是運行在老的版本上,則需要進行一個判斷,此時碰到一個問題是,在Google官方推薦中,在判斷app啟動並執行系統是否在Android M上時,它的判斷是如下:Build.VERSION.CODENAME.equals("MNC");實際發現這句卻無效的,改用Build.VERSION.SDK_INT >= 23的,需要另外特殊處理。
  針對SYSTEM_ALERT_WINDOW許可權,需要向系統發送一個ACTION_MANAGE_OVERLAY_PERMISSION。這樣一個動作,同時可以用Settings.canDrawOverlays() 方法進行判斷之前是否已經授權過了。

           //檢查開啟浮窗的特殊許可權是否被授權            if (!Settings.canDrawOverlays(getApplicationContext())) {                requestAlertWindowPermission();            }

  針對WRITE_SETTINGS許可權,需要向系統發送一個ACTION_MANAGE_WRITE_SETTINGS 這樣一個動作,同時可以用Settings.System.canWrite().方法進行判斷之前是否已經授權過了。
5錄屏應用的流程研究
  錄屏應用的實現思路大致是這樣:
1、拋棄應用內介面按鈕開啟關閉錄屏,採用浮動小表徵圖和點擊出現的大表徵圖實現錄屏按鍵。浮動表徵圖的實現通過FloatWindowService,它繼承自Service類,這樣可以方便地建立只有浮動表徵圖的布局,在Activity等地方利用startService(Intent intent)方法開啟服務。
2、我們希望使用者可以在介面隨時拖動小表徵圖,小表徵圖位置可變,在點擊小表徵圖時,出現大表徵圖並且,點擊按鈕、以及表徵圖外圍,大表徵圖移除,重建小表徵圖。
3、映像儲存為MP4格式,路徑為手機sdcard的Movies檔案目錄,自建的檔案夾RecordVideo。
4、浮動小球的優先順序為一般應用的最頂層,即除了狀態列下拉式清單外,小球總是可見的,這要得益於Service類的性質了。雖然在看電視等環境下會比較不適合,但該設計能讓使用者隨時、方便地錄取到想要的螢幕映像。
一、截屏請求結果資料共用類ShotApplication
  在上面已經提到,螢幕擷取需要使用者同意,初次運行時會有請求對話方塊,同意之後才能繼續,否則程式會終止。既然需要使用者選擇後的資訊,那在發出截屏請求時就不能用簡單的startActivity(Intent intent)方法,而是要用startActivityForResult(Intent intent, intresquestCode)方法。但是Service類中startActivityForResult(Intent intent, int resquestCode)方法不可用,確切的說是不存在可供子類重載的onActivityResult(int resquestCode, int resultCode, Intent data) 方法。但現實是ScreenrecordService類在實現錄屏過程中又要用到後面兩個傳回值(resultCode與data)來構建MediaProjection類的對象。
  如果直接在Activity中進行錄屏不就可以了嗎,這樣做沒有問題。但是問題在於我們需要在任何想截取螢幕的時候就能快速、方便地進行,即需要藉助利用FloatWindowService實現並浮動在一般性應用視窗之上的小球。而在Activity中實現的話就達不到這種效果了,往往能擷取的只能是應用本身介面,或者是將其隱藏後的下一層介面,總之做不到想要即可得的效果。
  所以,首要問題是讓類ScreenrecordService的對象能得到這兩個資料。另外得注意,Bundle可以完成一般資料的載入並賦給Intent類對象,然後一起發送給目標類,但參數data本身就是Intent類型的。我們在這裡採取資料共用的思路,利用繼承自Application類的子類ShotApplication,然後定義需要共用的成員變數(有些是其他類的對象)。
  其中MediaProjectionManager類對象在發送截屏請求和構建MediaProjection類對象時均會用到,至於成員值的設定及擷取很直觀,就不解釋了。那麼資料的傳遞就明朗了:先從主程式類MainActivity中存入共用類ShotApplication,然後服務類ScreenrecordService從共用類ShotApplication中提取出來。
二、主程式類MainActivity所做5件事
1)首先判斷android平台,如果是api>=23,先進行許可權的動態申請。
2)向使用者提出截屏請求。這正是類MainActivity做的第1件事。當然,在這之前需要擷取類MediaProjectionManager執行個體。
   由於onCreate()方法是應用開啟後自動調用的,在點擊start按鈕後startIntent隨即被調用,所以第一次運行時,這一行截屏請求代碼也會自動執行。如果是應用安裝後第一次開啟,那麼就會彈出截屏許可權允許對話方塊,需要使用者授權。
3)類MainActivity做的第2件事就是將使用者操作所返回的值和初始擷取的類MediaProjectionManager執行個體寫入資料共用類ShotApplication中。
4)類MainActivity做的第3件事就是肯定是開啟浮窗服務。
5)類MainActivity做的第4件事是將自身銷毀(finish()),之後的控制權就交給服務類的浮動小表單。
三、服務類FloatWindowService完成大小懸浮窗的建立、移除和控制
從onStartCommand(Intent intent, int flags, int startId)方法開始,開啟定時器,每隔0.5秒調用RefreshTask()。
1)如果當前介面是案頭,且沒有懸浮窗顯示,則建立懸浮窗,調用createSmallWindow()。
2)當前介面是返回應用的介面,且有懸浮窗顯示,則移除懸浮窗,調用removeSmallWindow()和removeBigWindow()。
3)在類FloatWindowSmallView中監聽表徵圖的點擊事件,調用createBigWindow()。
4)在類FloatWindowBigView中監聽按鈕的點擊事件:
  1.點擊開始錄製的時候,移除大懸浮窗,建立小懸浮窗,並開始ScreenrecordService。
  2. 點擊結束錄製的時候,移除大懸浮窗,建立小浮窗,並停止ScreenrecordService。
四、服務類ScreenrecordService完成錄屏工作
(1)在onCreate()中調用了createRecordDir()、initRecorder()和prepareRecorder()方法。
  1)createRecordDir()用來建立錄製視頻的儲存路徑;
  2)initRecorder()負責MediaRecorder的初始化;
  3)prepareRecorder()負責調用MediaRecorder執行個體的prepare()方法。
(2)在onStartCommand中調用startrecord()負責開啟截屏,建立一個Surface,並錄製視頻。
(3)在該ScreenrecordService的onDestroy()中停止視頻的錄製,並停止截屏。
(4)發送廣播,通知系統媒體庫更新。
6總結
(1)截屏功能是在Android5.0才開始加入的(或者說開放出來的)新介面,所以,該方法只能用於5.0以上的Android版本(api21以上)。
(2)浮窗通過WindowManager這個類來實現的,調用這個類的addView方法可以添加一個懸浮窗。
(3)應用MediaRecorder時應注意,與MediaPlayer相似使用MediaRecorder錄音錄影時需要嚴格遵守狀態圖說明中的函數調用先後順序,在不同的狀態調用不同的函數,否則會出現異常。
(4)Android 6.0之後,新的許可權擷取方式除了要求像之前版本一樣在AndroidManifest檔案中靜態申請之外,應用還需根據需要請求的許可權,方式採用向使用者顯示一個請求許可權的對話方塊,才能擷取相應的許可權。
(5)在Service類中startActivityForResult(Intent intent, int resquestCode)方法不可用,確切的說是不存在可供子類重載的onActivityResult(int resquestCode, int resultCode, Intent data) 方法,我們可以採用共用類的思路擷取resultCode和data。
 

Android錄屏應用開發研究

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.