I have a fragment which runs camera in it. and then in the main activity, I instantiate the class by passing the context to it
Camera_play fragment1 = new Camera_play(this);
I have other fragments which play images and videos.
I rotate the device after 10 seconds to landscape mode and I need all the fragments to keep playing .
Timer timer = new Timer();
TimerTask action = new TimerTask() {
public void run()
{
runOnUiThread(new Runnable() {
public void run(){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
});
}
};
timer.schedule(action, 10000);
I handle the objects as follows :
if(savedInstanceState == null)
{
frg1 = new Camera_play(this);
frg2 = new play_video();
android.support.v4.app.FragmentManager manager= getSupportFragmentManager();
transaction=manager.beginTransaction();
transaction.add(R.id.video, frg1,"frag1");
transaction.add(R.id.camera, frg2,"frag2");
transaction.commit();
}
else
{
**frg1 = new Camera_play(this);** ?????
frg2 = (play_video) getSupportFragmentManager().findFragmentByTag("frag2");
}
This works fine for the video but the app crashes for the video stating the instantation failed and the class msut have an empty constructor.
How do I make the camera to continue playing after I rotate the device?
Related
Brief description of application:
I have Cordova/Ionic application and Custom Cordova plugin for native code execution.
Plugin contains separate CameraActivity (extends FragmentActivity) to work with Camera (parts of code based on Camera2Basic example).
On launch Activity displays AnaliseFragment, where application captures every Camera frame and passes image to the analyser on backround thread.
Execution steps are:
User presses button on Cordova UI
Cordova executes native plugin method via cordova.exec(..)
Native plugin starts CameraActivity for result via cordova.startActivityForResult(..)
CameraActivity displays AnaliseFragment
AnaliseFragment starts Camera capture session with two surfaces: first is displayed on TextureView and second analised by ImageAnaliser
Problem:
Rarely and randomly UI stops reacting on user and runnables not executed on UI thread. At the same time background threads continue working as normal: camera output is visible on TextureView and ImageAnaliser continue receive images from Camera.
Does anybody have any suggestion how to find/debug reason of such behavior? Or any ideas what can cause this?
I already tried:
log every lifecycle event of CameraActivity/AnaliseFragment = no calls between app normal state and ANR
add WAKELOCK to keep Cordova MainActivity alive = didn't help
log(trace) every method in AnalilseFragment and ImageAnaliser = nothing suspicious
Here is simplified code of AnaliseFragment:
public class AnaliseFragment extends Fragment {
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
private ImageAnalyser mImageAnalyser;
// listener is attached to camera capture session and receives every frame
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
#Override
public void onImageAvailable(ImageReader reader) {
Image nextImage = reader.acquireLatestImage();
mBackgroundHandler.post(() ->
try {
mImageAnalyser.AnalizeNextImage(mImage);
}
finally {
mImage.close();
}
);
}
};
#Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
mImageAnalyser = new ImageAnalyser();
mImageAnalyser.onResultAvailable(boolResult -> {
// Runnable posted, but never executed
new Handler(Looper.getMainLooper()).post(() -> reportToActivityAndUpdateUI(boolResult));
});
}
#Override
public void onResume() {
super.onResume();
startBackgroundThread();
}
#Override
public void onPause() {
stopBackgroundThread();
super.onPause();
}
private void startBackgroundThread() {
if (mBackgroundThread == null) {
mBackgroundThread = new HandlerThread("MyBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
}
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Simplified code for ImageAnalyser:
public class ImageAnalyser {
public interface ResultAvailableListener {
void onResult(bool boolResult);
}
private ResultAvailableListener mResultAvailableListener;
public void onResultAvailable(ResultAvailableListener listener) { mResultAvailableListener = listener; }
public void AnalizeNextImage(Image image) {
// Do heavy analysis and put result into theResult
mResultAvailableListener.onResult(theResult);
}
}
There is some long-running operation in UI-thread. Try profile your app to figure out what does block your main thread.
After hours of profiling, debugging and code review I found, that
issue was caused by incorrect View invalidation from background thread
View.postInvalidate() method must be used - this method checks if View is still attached to window and then do invalidation. Instead I wrongly used View.invalidate(), when process my custom message from MainLooper, which rarely caused failures and made MainLooper stop processing any more messages.
For those who maybe have same problem I added both correct and incorrect code.
CORRECT:
public class GraphicOverlayView extends View { ... }
// Somewhere in background thread logic:
private GraphicOverlayView mGraphicOverlayView;
private void invalidateGraphicOverlayViewFromBackgroundThread(){
mGraphicOverlayView.postInvalidate();
};
WRONG:
public class GraphicOverlayView extends View { ... }
// Somewhere in background thread logic:
private GraphicOverlayView mGraphicOverlayView;
private final int MSG_INVALIDATE_GRAPHICS_OVERLAY = 1;
private Handler mUIHandler = new Handler(Looper.getMainLooper()){
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE_GRAPHICS_OVERLAY:{
GraphicOverlayView overlay = (GraphicOverlayView)msg.obj;
// Next line can cause MainLooper stop processing other messages
overlay.invalidate();
break;
}
default:
super.handleMessage(msg);
}
}
};
private void invalidateGraphicOverlayViewFromBackgroundThread(){
Message msg = new Message();
msg.obj = mGraphicOverlayView;
msg.what = MSG_INVALIDATE_GRAPHICS_OVERLAY;
mUIHandler.dispatchMessage(msg);
};
I am writing an android app that transforms a nexus 7 tablet into an info panel which should show certain information in fullscreen - no user interaction is necessary.
For each type of information, i created a fragment with the corresponding UI. What i want to achieve is that the main activity switches through the fragments each 5 seconds.
Within each fragment, I implemented a simple callback telling the Activity that its AsyncTask finished getting the data and building the (fullscreen) UI:
((MainActivity)getActivity()).fragmentFinishedLoading();
Within my activity, the method looks like this:
public void fragmentFinishedLoading(){
finishedFragments++;
while(finishedFragments == amountOfFragments){
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.fragment_A));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.fragment_B));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.fragment_C));
fragmentTransaction.show(fragmentManager.findFragmentById(R.id.fragment_D));
fragmentTransaction.commit();
fragmentManager.executePendingTransactions();
Thread.sleep(5000);
}
}
Note that the code above did not yet include functionality to dynamically change which fragment should be hidden or showed.
My problem: after
fragmentTransaction.commit();
and even after
fragmentManager.executePendingTransactions();
the fragments did not change at all. I checked the commit() method and found out that it is async. I know that the Thread.sleep() is bad practice since it freezes the UI, but since no UI interaction is necessary i could live with that. However, since i sent the UI thread to sleep, probably the fragmentTransaction sleeps as well(?)
I am searching for a way to hide/show a certain amount of fragments after a given time without any user interaction - any help on that is greatly appreciated.
EDIT:
rguerra's CountDownTimer suggestion did the trick. For the sake of transparency, this is the solution that works for me:
public void fragmentFinishedLoading(){
finishedFragments++;
if(finishedFragments == amountOfFragments){
new FragmentTimer(5000, 1000, 1).start();
}
}
private class FragmentTimer extends CountDownTimer{
int fId;
static final int amountOfFragments = 4;
public FragmentTimer(long millisInFuture, long countDownInterval, int fragmentId) {
super(millisInFuture, countDownInterval);
fId = fragmentId;
}
#Override
public void onTick(long millisUntilFinished) {
}
#Override
public void onFinish() {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if(fId == 1){
fragmentTransaction.show(fragmentManager.findFragmentById(R.id.A_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.B_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.C_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.D_fragment));
}
else if (fId == 2){
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.A_fragment));
fragmentTransaction.show(fragmentManager.findFragmentById(R.id.B_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.C_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.D_fragment));
}
else if (fId == 3){
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.A_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.B_fragment));
fragmentTransaction.show(fragmentManager.findFragmentById(R.id.C_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.D_fragment));
}
else if (fId == 4){
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.A_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.B_fragment));
fragmentTransaction.hide(fragmentManager.findFragmentById(R.id.C_fragment));
fragmentTransaction.show(fragmentManager.findFragmentById(R.id.D_fragment));
}
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragmentTransaction.commit();
fragmentManager.executePendingTransactions();
if(fId == amountOfFragments){
fId = 1;
}
else{
fId++;
}
new FragmentTimer(5000, 1000, fId).start();
}
}
Use replace Fragment.
example for show R.id.fragment_D
new CountDownTimer(5000, 1000) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.R.id.fragment_D, (Fragment) new FragmentAFragment(), FRAGMENTA_TAG);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
}.start();
http://developer.android.com/reference/android/os/CountDownTimer.html
There are several ways to do this. You can use a Handler OR Alarm Manager with a PendingIntent OR a Timer.
Based on what your design sounds like I would suggest a ViewPager might be a good choice for managing the transitions between your fragments.
Handler mHandler;
public void useHandler() {
mHandler = new Handler();
mHandler.postDelayed(mRunnable, 5000);
}
private Runnable mRunnable = new Runnable() {
#Override
public void run() {
mMyFragmentStatePagerAdapter.myViewPagerIncrementFunction();
useHandler();
}
};
Cancel it with: mHandler.removeCallbacks(mRunnable);
I'm using Loaders in my android App to retrieve data from the web. in my onCreate() method I initialize the loader. Once the data is retrieved, I can access them in onLoadFinished() method. This is working fine.
I'm trying to add a time-out so that if the request takes more than 15 seconds, I destroy the loader and re-initiate it. I'm using the following to achieve this:
private void timeOutTimer()
{
final Timer t = new Timer();
t.schedule(new TimerTask() {
public void run() {
myHandler.post(recreateLoader);
t.cancel();
}
}, 15000);
}
final Runnable recreateLoader = new Runnable() {
public void run() {
if(getLoaderManager().getLoader(MY_LOADER) != null || getLoaderManager().getLoader(MY_LOADER) != null)
{
Toast.makeText(getApplicationContext(),"Reconnecting..",Toast.LENGTH_SHORT).show();
getLoaderManager().destroyLoader(MY_LOADER);
ReInitiateLoader();
}
}
}
};
The problem is that onLoadFinished is never called. It seems that initiating the Loader in a thread doesn't work. Is there a solution for this?
I think I know why this happens.
When rotating the screen the Activity gets destroyed and recreated and the runOnUiThread() method is referencing the old(destroyed) Activity which isn't valid anymore and so android throws a runtimeexception.
To demonstrate this I have this code:
#ContentView(R.layout.activity_start)
public class StartActivity extends RoboSherlockFragmentActivity {
#Inject
private EventManager eventManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Fragment newFragment = new WelcomeFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, newFragment).commit();
new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(2000);
//Do some short initialization here like read shared prefs
//and then decide for example whether to login or skip the login
//If the rotation happens while sleeping the app will certainly crash
runOnUiThread(new Runnable() {
#Override
public void run() {
addFragmentToStack();
}
});
} catch (Exception e) {
// TODO: handle exception
}
}
}).start();
}
}
void addFragmentToStack() {
Fragment newFragment = new LoginOrRegisterFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, newFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
}
}
Do I have to use an asynctask for some easy task like that? If so how I handle the rotation then? Because the callback reference would be faulty either.
try this
new Handler(getMainLooper()).postDelayed(new Runnable(){
if(!isFinishing()){
addFragmentToStack();
}
},200);
instead of your thread code
isFinishing() is called when the activity is in the process of being destoryed
It seems that all you're trying to do is to make the task executed after 200 ms have passed.
there is no need to open a new thread for that, a handler will do
2ndly if you want to ensure that the code will be executed on the main thread
you create the handler calling for the main looper
Handler(getMainLooper()) and it will make this handler execute its task on the main thread
AsyncTask will generate the same crash. You can Simply override onPause() and find a way to notify the thread that Activity is about to be destroyed. Like checking for null value of a variable.
public void run() {
if(someVariable != null)
addFragmentToStack();
}
Or you can use a callback fragment (without view) to keep track of configuration change. This article is a great way to start. This method I prefer because it is a very clean approach.
I had a similar problem and solved it by using a retain fragment where I store a reference to the Activity on every OnCreate e.g. retainFragment.activity = this; . In runOnUiThread I check if the retained reference is the current Activity e.g. if (retainFragment.activity == MainActivity.this)..
I have Multi-Shot facility in my camera application. I am using following short of code to do that.
if (TIMER_STARTED) {
multishotTimer.cancel();
multishotTimer.purge();
multishotTimer = null;
TIMER_STARTED = false;
} else {
multishotTimer = new Timer();
multishotTimer.schedule(new TimerTask() {
#Override
public void run() {
TIMER_STARTED = true;
Camera camera = surfaceView.getCamera();
camera.takePicture(null, null,
new HandlePictureStorage());
}
}, 1000, 5000L);
}
Here, TIMER_STARTED is boolean flag which indicate whether timer is started or not. HandlePictureStorage is class which handles PictureCallback.
Question:
When first time i click on "MultiShot" button, it will start timer and take picture every 5 seconds. To stop timer, I one more time click on same button. But if I continuously clicking on button, application hangs and force stopped. Then after I need to switch off my device due to camera is used by stopped service and can't release it lightly. How can I manage start and stop timer?
You don't need TIMER_STARTED to choose whether the button will start or stop the multishot: you can simply check if (multishotTimer != null).
But even after this fix, clicking too fast may be dangerous: you should not create a new Timer between takePicture and HandlePictureStorage.
private bool isCapturing = false;
#Override
public void onClick(View v) {
if (multishotTimer != null) {
multishotTimer.cancel();
multishotTimer.purge();
multishotTimer = null;
}
else if (!isCaptureing) {
multishotTimer = new Timer();
multishotTimer.schedule(new TimerTask() {
#Override
public void run() {
isCapturing = true;
Camera camera = surfaceView.getCamera();
camera.takePicture(null, null,
new HandlePictureStorage());
}
}, 1000, 5000L);
}
}
class HandlePictureStorage implements ... {
#Override
public void onPictureTaken(...) {
isCaptureing = false;
}
}
You need to move TIMER_STARTED = true; from the timer task to the else part, for example after scheduling the timer.
To improve the performance, you should create a TimerTask field instead of recreating an anonymous class every time you create and start the timer.