1

所以我在尝试在 Android 中捕获我的应用程序屏幕时遇到了问题。情况如下:

我想使用 View.getDrawingCache() 方法连续捕获应用程序屏幕并检测屏幕的哪个部分发生了变化。我的应用程序基于一个包含 5 个活动的 TabHost,我想捕获 tabhost 中发生的所有事情。我还希望屏幕捕获代码在后台运行,为此我创建了一个 AsyncTask 实例来捕获图像并检查是否发生了变化。问题是 getDrawingCache() 方法有时会导致应用程序崩溃并引发 CalledFromWrongThreadException。

我已经做了一些测试,这就是我发现的。首先,我什至没有在 tabhost 中的活动之间进行更改,所以只有第一个是可见的。第一个活动有一个 ScrollView,一旦我使用“android:scrollbars='none'”禁用滚动条,捕获就会起作用,但如果我重新启用它们,它会崩溃并出现相同的异常。我认为问题在于我正在 doInBackground 方法中进行屏幕捕获,而 Android 出于某种原因不喜欢这样(好吧,我知道原因 - 它试图在主线程以外的线程中绘制缓存.. .)。

所以我的问题是有人知道我如何在后台实现录制吗?

这是代码的主要部分,以使事情变得清晰......

public class TRCTargetMainActivity extends TabActivity {
private static final Logger tracelog = Logger.getLogger( TRCTargetMainActivity.class.getName() );

private boolean isVisible;
private int selectedIndex;
private Bitmap latestScreen, currentScreen;
private boolean stop = false;
private ScreenCaptureThread scThread;

private TabHost mTabHost;
private MyNotificationReceiver receiver;
private BadgeView chatBadge, transfersBadge;

public void onCreate( Bundle savedInstanceState ) {
    super.onCreate(savedInstanceState);

    requestWindowFeature( Window.FEATURE_NO_TITLE );
    setContentView( R.layout.activity_main );

    mTabHost = getTabHost();

    buildTab( "deviceSpec", getString( R.string.activity_trctargetmain_title_info ), R.drawable.ic_tab_device_info, DeviceInfoActivity.class );
    buildTab( "fileExplorerSpec", getString( R.string.activity_trctargetmain_title_transferfolder ), R.drawable.ic_tab_transfer, FileExplorerActivity.class );
    buildTab( "chatSpec", getString( R.string.activity_trctargetmain_title_chat ), R.drawable.ic_tab_chat, ChatActivity.class );
    buildTab( "transfersSpec", getString( R.string.activity_trctargetmain_title_transfers ), R.drawable.ic_tab_transfers, TransfersActivity.class );
    buildTab( "settingsSpec", getString( R.string.activity_trctargetmain_title_settings ), R.drawable.ic_tab_settings, SettingsActivity.class );

    Bundle extras = getIntent().getExtras();
    if ( extras != null ) {
        selectedIndex = extras.getInt( Settings.EXTRA_SELECTED_TAB );

        mTabHost.setCurrentTab( selectedIndex );
    }

    mTabHost.setOnTabChangedListener( new OnTabChangeListener() {
        public void onTabChanged( String arg0 ) {         
            selectedIndex = mTabHost.getCurrentTab();
        }       
    } );  

    chatBadge = new BadgeView( this, mTabHost.getTabWidget(), 2 );
    transfersBadge = new BadgeView( this, mTabHost.getTabWidget(), 3 );

    initializeBroadcastReceiver();
    configureTabHostBackground();
    checkDirectoryExistence();
    checkTargetServiceState();

    doStartScreenCapture();
}

...

public void doStartScreenCapture() {
    stop = false;

    if ( scThread == null ) {
        scThread = new ScreenCaptureThread();
        scThread.execute();
    }
}
...

private Bitmap getScreenBitmap() {
    RelativeLayout r = (RelativeLayout) this.findViewById( R.id.rootLayout );
    View v = r.getRootView();
    v.setDrawingCacheEnabled( true );

    Bitmap bm = v.getDrawingCache();
    if ( bm == null )
        return bm;

    int[] pixels = new int[ bm.getWidth() * bm.getHeight() ];
    bm.getPixels( pixels, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight() );

    return Bitmap.createBitmap( pixels, bm.getWidth(), bm.getHeight(), Bitmap.Config.ARGB_8888 );
}
...

private class ScreenCaptureThread extends AsyncTask<String,Integer,Boolean> {

    private void sendScreenUpdate( int[] data, int row, int col ) {
        ByteBuffer byteBuffer = ByteBuffer.allocate( data.length * 4 );        
        IntBuffer intBuffer = byteBuffer.asIntBuffer();
        intBuffer.put( data );

        byte[] array = byteBuffer.array();

        // send the screen update
    }

    private boolean checkForScreenChanges() {
        int[] pixels1 = new int[ Settings.SCREEN_BLOCK_SIZE * Settings.SCREEN_BLOCK_SIZE ];
        int[] pixels2 = new int[ Settings.SCREEN_BLOCK_SIZE * Settings.SCREEN_BLOCK_SIZE ];

        int numCols = currentScreen.getWidth() / Settings.SCREEN_BLOCK_SIZE;
        int numRows = currentScreen.getHeight() / Settings.SCREEN_BLOCK_SIZE;
        int x = 0;
        int y = 0;

        boolean differenceFound = false;
        for ( int i = 0; i < numRows; i++ ) {
            for ( int j = 0; j < numCols; j++ ) {
                latestScreen.getPixels( pixels1, 0, Settings.SCREEN_BLOCK_SIZE, x, y, Settings.SCREEN_BLOCK_SIZE, Settings.SCREEN_BLOCK_SIZE );
                currentScreen.getPixels( pixels2, 0, Settings.SCREEN_BLOCK_SIZE, x, y, Settings.SCREEN_BLOCK_SIZE, Settings.SCREEN_BLOCK_SIZE );

                if ( !Arrays.equals( pixels1, pixels2 ) ) {
                    sendScreenUpdate( pixels2, i, j );

                    differenceFound = true;
                }

                Arrays.fill( pixels1, 0 );
                Arrays.fill( pixels2, 0 );

                x += Settings.SCREEN_BLOCK_SIZE;
            }

            x = 0;
            y += Settings.SCREEN_BLOCK_SIZE;
        }

        return differenceFound;
    }

    @Override
    protected Boolean doInBackground( String... arg0 ) {
        System.out.println( "SCREEN CAPTURE THREAD STARED" );

        currentScreen = getScreenBitmap();
        latestScreen = getScreenBitmap();

        while ( !stop ) {
            try {
                Thread.sleep( 3000 );
            } catch ( Exception e ) {}
            if ( latestScreen != null && currentScreen != null ) {
                if ( checkForScreenChanges() ) {
                    latestScreen = getScreenBitmap();
                    System.out.println( "SCREEN UPDATED" );
                }
            }
            if ( currentScreen != null && !currentScreen.isRecycled() )
                currentScreen.recycle();

            currentScreen = getScreenBitmap();
        }

        return true;
    }

    @Override
    protected void onPostExecute( Boolean result ) {
        System.out.println( "SCREEN CAPTURE THREAD STOPPED" );
    }
    }
}

这是活动的 xml 文件...

<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id="@+id/rootLayout"
        >

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_above="@android:id/tabs">

            <RelativeLayout android:id="@+id/emptylayout1" android:orientation="vertical"
                 android:layout_width="fill_parent" android:layout_height="fill_parent"/>
            <RelativeLayout android:id="@+id/emptylayout2" android:orientation="vertical"
                 android:layout_width="fill_parent" android:layout_height="fill_parent"/>
            <RelativeLayout android:id="@+id/emptylayout3" android:orientation="vertical"
                 android:layout_width="fill_parent" android:layout_height="fill_parent"/>
        </FrameLayout>

        <TabWidget
            android:id="@android:id/tabs"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="-4dip" />
    </RelativeLayout>
</TabHost>

最后,这是抛出的异常......

01-21 18:11:40.070: E/AndroidRuntime(11279): FATAL EXCEPTION: AsyncTask #1
01-21 18:11:40.070: E/AndroidRuntime(11279): java.lang.RuntimeException: An error occured while executing doInBackground()
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.os.AsyncTask$3.done(AsyncTask.java:200)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:274)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at java.util.concurrent.FutureTask.setException(FutureTask.java:125)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:308)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1088)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:581)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at java.lang.Thread.run(Thread.java:1019)
01-21 18:11:40.070: E/AndroidRuntime(11279): Caused by: android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewRoot.checkThread(ViewRoot.java:3055)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewRoot.invalidateChild(ViewRoot.java:657)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewRoot.invalidateChildInParent(ViewRoot.java:683)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.invalidateChild(ViewGroup.java:2514)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.View.invalidate(View.java:5479)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.View.awakenScrollBars(View.java:5372)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.View.awakenScrollBars(View.java:5264)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.widget.ScrollView.onOverScrolled(ScrollView.java:820)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.View.overScrollBy(View.java:9100)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.widget.ScrollView.computeScrollBounce(ScrollView.java:1325)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.widget.ScrollView.computeScroll(ScrollView.java:1366)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.drawChild(ViewGroup.java:1562)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.View.draw(View.java:7083)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.drawChild(ViewGroup.java:1646)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.View.draw(View.java:7083)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.widget.FrameLayout.draw(FrameLayout.java:357)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.drawChild(ViewGroup.java:1646)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.drawChild(ViewGroup.java:1644)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.drawChild(ViewGroup.java:1644)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.drawChild(ViewGroup.java:1644)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.drawChild(ViewGroup.java:1644)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.View.draw(View.java:7083)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.widget.FrameLayout.draw(FrameLayout.java:357)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.drawChild(ViewGroup.java:1646)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.View.draw(View.java:7083)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.widget.FrameLayout.draw(FrameLayout.java:357)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2119)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.View.buildDrawingCache(View.java:6842)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.View.getDrawingCache(View.java:6628)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.view.View.getDrawingCache(View.java:6593)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at com.ibm.trctarget.TRCTargetMainActivity.getScreenBitmap(TRCTargetMainActivity.java:158)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at com.ibm.trctarget.TRCTargetMainActivity.access$6(TRCTargetMainActivity.java:153)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at com.ibm.trctarget.TRCTargetMainActivity$ScreenCaptureThread.doInBackground(TRCTargetMainActivity.java:391)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at com.ibm.trctarget.TRCTargetMainActivity$ScreenCaptureThread.doInBackground(TRCTargetMainActivity.java:1)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at android.os.AsyncTask$2.call(AsyncTask.java:185)
01-21 18:11:40.070: E/AndroidRuntime(11279):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:306)
01-21 18:11:40.070: E/AndroidRuntime(11279):    ... 4 more

非常感谢任何帮助:)

4

1 回答 1

0

愚蠢而简单的方法是将需要在主线程上运行的东西包装在runOnUiThread(...)

于 2013-01-21T18:55:26.777 回答