mtsahakis / MediaProjectionDemo

One Activity sample app of using Android Lollipop MediaProjection API to capture device screenshots
Other
210 stars 76 forks source link

Some questions: memory leak, looper, moving to service... #7

Closed AndroidDeveloperLB closed 3 years ago

AndroidDeveloperLB commented 7 years ago
  1. Since many fields (mHandler for example) are in the activity, won't it mean that it can leak upon screen orientation change?
  2. Isn't it the same to just instantiate the handler as : "mHandler = new Handler() " , compared to the looper in the code?
  3. What would it take to move all the needed code to a service, that upon calling a specific function there will trigger a screenshot?
mtsahakis commented 7 years ago

Hi,

You have some valid points, hope to answer them the best I can:

  1. The Activity was leaking because there is an anonymous Thread instantiated in onCreate() method that gets called in every orientation change. That Thread has a Looper associated with it, so it's run() method blocks and never completes (like the UI Thread). I crosschecked that with hprof tool, and Activities were indeed leaking. I switched to explicit android:configChanges="orientation|screenSize" in AndroidManifest.

  2. I created the Handler Thread discussed above so that a separate Thread manages all screens capture heavy lifting. If we just add mHandler = new Handler() then all screen capture code runs on the UI Thread. If your app is comprised of a single Activity, like the demo provided, that might not be that important.

  3. The code in this repo serves as a quick example to MediaProjection API, nothing more. In my applications I have refactored everything to a Service. It is rather trivial to do, I could provide you with code if you have a hard time refactoring the example to a Service. I will not do it though as part of this project, as it defeats the purpose of having a one-class-example and not something more convoluted.

If you decide to do it yourself, beware to manage:

Regards,

Manos

AndroidDeveloperLB commented 7 years ago
  1. This is not a valid fix, as orientation change is only one of many ways that the activity can be destroyed and re-created. Isn't there a better way you can think of?
  2. I see. Clever.
  3. Yes please, if you can. I think a service is much more suitable when using this API, as capturing the current activity is already possible using a different API, and this one is usually meant to be used when the app is in the background, to capture other apps' screenshots. I would be happy if you provide a minimal project that has the service, or if you tell what to move where.
mtsahakis commented 7 years ago

I agree with point (1) and the best way to do it is to move everything to a service. I will try to upload a gist as soon as I can.

AndroidDeveloperLB commented 7 years ago

Wow thank you so much!

mtsahakis commented 7 years ago

Look at the two gists bellow for Service and corresponding Activity.

https://gist.github.com/mtsahakis/a4dca46b80cb8ac2cd100a7a52f65b1d https://gist.github.com/mtsahakis/5c709bbd9cf5074a377e6e3114c0e4b6

Hope it helps,

Manos

AndroidDeveloperLB commented 7 years ago

Works great! It takes a while till it stops to takes screenshots though. Anyway thanks !

I have another question: why do you synchronize in the "onOrientationChanged" call ? It's on the UI thread anyway, so it doesn't supposed to be called on multiple threads...

mtsahakis commented 7 years ago

Now that I look at the code, synchronization block might not be needed.

I see that createVirtualDisplay() is always called from the main Thread (orientation change and onStartCommand), so it could be a relic of past times.

Can you try without it and see how the app behaves?

Try rotating the device, starting/stopping projection, etc.

AndroidDeveloperLB commented 7 years ago

Seems to still work fine. However, I've noticed something that isn't related to this (happens on all versions, now that I've checked) : Sometimes the entire screen flashes. Maybe it occurs when it takes a picture? Is it maybe intentional by the Android OS itself? To give feedback that the screen was captured? How often does the sample app takes a picture anyway? I can't find an indication of time there... Does it take one after another without pausing ?

mtsahakis commented 7 years ago

Cool, I will update the gist without the sync block so as not to confuse anyone, after I give it some testing my self.

I have never seen flashes to be honest. As far as I know there is no indication by the OS that screen is captured or anything, just the Cast Screen icon on the system bar. You are not referring to Developer Options -> Monitoring -> Strict Mode Enabled, are you? I have tested the sample app on many Lollipop+ devices and never seen any flashes...

The app receives images as soon as images are ready from the OS, there is no throttling or anything like that. You can introduce throttling (add some sleep on ImageAvailableListener callback) and check if flashes go away.

AndroidDeveloperLB commented 7 years ago

When starting to take screenshots, I went to the settings screen of the OS, scrolled for a while here and there, and noticed the flashing. It's a very short flash, but it exists (shows a black screen for a fraction of a second). It didn't occur on the app itself, but outside of it. It occurred even here on the Github sample. Strict mode isn't enabled. Tested on Nexus 5x with Android 7.1.1

About the screenshots frequency, so you say that it takes a new screenshot right after it got the previous one, without doing anything in between. This means that the speed of it depends mainly on the hardware, right ? The faster it is, the more screenshots per minute it can take...

mtsahakis commented 7 years ago

Still can't see the flash, I will notify my QA team for that, could be intermittent or something. It is hardware dependant, I have seen many variations between devices.

AndroidDeveloperLB commented 7 years ago

Here's a video showing that it flashed (took it from another device) : https://file.town/download/6lsv393bpcj37z2fond0rjpfe

AndroidDeveloperLB commented 7 years ago

Say, instead of a thread that calls the looper, isn't is nicer to use HandlerThread : https://developer.android.com/reference/android/os/HandlerThread.html ?

mtsahakis commented 7 years ago

I do agree, this is how I have it on my internal implementations, though it wouldn't make much difference. I tried to download the video from the service you provided but couldn't, could be due to the fact that I am in a corporate network. I will give it a go when I get back home.

AndroidDeveloperLB commented 7 years ago

OK, thank you.

mtsahakis commented 7 years ago

I am still not able to download the video file but I got to reproduce the issue on my Nexus 5X device with latest Android N. Looks very similar to:

https://code.google.com/p/android/issues/detail?id=197377

Even the steps to reproduce look awfully familiar. I can't help more, I added a watch on the issue, I suggest you do the same. Issue status is "assigned" but is open for quite some time unfortunately.

AndroidDeveloperLB commented 7 years ago

How is it similar? I wrote that while it's active, screen capture causes a flash of black screen. About the file, I think I chose a bad website to upload to. Will upload again tomorrow.

mtsahakis commented 7 years ago

When you experience flashes, you will observe in logcat logs like bellow, identical to the bug report:

E/qdoverlay: Bad ov dump: mdp_overlay z=2 alpha=255 mask=-1 flags=0x20000 id=-1 E/qdoverlay: src msmfb_img w=1152 h=126 format=13 MDP_RGBA_8888 E/qdoverlay: src_rect mdp_rect x=0 y=0 w=1080 h=126 E/qdoverlay: dst_rect mdp_rect x=0 y=1794 w=1080 h=126

This only occurs when a MediaProjection is active.

Other than that I do not see anything helpful in the logs.

I was able to reproduce the flashes by pressing back and forth recents on my 5X device.

AndroidDeveloperLB commented 7 years ago

Indeed. I've now reported this issue: https://code.google.com/p/android/issues/detail?id=231642

AndroidDeveloperLB commented 7 years ago

Say, do you know if it's possible that instead of capturing the whole screen, to capture a part of it, and even the bitmap of an ImageView (of any app, not just the current app) ?

mtsahakis commented 7 years ago

Unfortunately I do not know how to achieve this. I have never had a similar request for an app, but it is interesting, so please share if you find anything on the matter.

AndroidDeveloperLB commented 7 years ago

ok I asked about it here: http://stackoverflow.com/q/41539245/878126

pawaom commented 7 years ago

I am referring to this https://gist.github.com/mtsahakis/a4dca46b80cb8ac2cd100a7a52f65b1d

I have been able to work with the code in Service , however my issue is the mainactvity stays in stack history of the app , how can we remove it from stack history, I have tried android:excludeFromRecents , android:noHistory , android:taskAffinity

My problem is I have a MAIN service which starts the screen shot permission activity, which in turn starts the screen shot service

now I dont want to have the screen shot permission activity in app stack history , how can this be done

I have referred to https://stackoverflow.com/questions/32381455/android-mediaprojectionmanager-in-service

and

https://stackoverflow.com/questions/33398211/how-do-i-get-a-media-projection-manager-without-disturbing-the-current-foregroun (I dont know how to use this , it seems most useful)

It can be best explained with the use of these apps https://play.google.com/store/apps/details?id=com.hecorat.screenrecorder.free

and

https://play.google.com/store/apps/details?id=com.duapps.recorder

In these apps once we give the permission, it is removed from stack history and the permission activity is not seen

please any guidance will be helpful again this seems to be the best solution ,

https://stackoverflow.com/questions/33398211/how-do-i-get-a-media-projection-manager-without-disturbing-the-current-foregroun

but I am new to Android please guide me for this

mtsahakis commented 7 years ago

Hi,

I am not sure about your exact use case, but here are my thoughts on your problem.

  1. I would advise against using android:excludeFromRecents , android:noHistory , android:taskAffinity. Think if the user is interacting with your screencapture Activity (the activity that pops the system dialog) and then presses the Home button. Will he be able to return to the screencapture Activity from recents?

  2. In Android, an Activity gets off the stack if the user presses the back button, or if finish() is explicitly called from application code. From the SO links you included in the problem description I see that most solutions call finish() once the result from the media projection system dialog is returned.

  3. Expanding on point (2), in my productions apps I have code similar to https://gist.github.com/mtsahakis/a46cafb8991af18a069e50758c29aab7 that explicitly calls finish() after the user result is intercepted. Of course, you should provide some additional UI component that stops media projection after it is started (perhaps a small button the user can press that would ultimately stop the media projection service (similar to startService(ScreenCaptureService.getStopIntent(this)); in https://gist.github.com/mtsahakis/5c709bbd9cf5074a377e6e3114c0e4b6).

Hope it helps,

Manos

pawaom commented 7 years ago

this was very helpful thanks

Angelk90 commented 5 years ago

@mtsahakis : Hi, congratulations on the project. I'm facing a similar problem but with some differences, can I ask you some tips or a hand on how I can solve my problem?