Activity Lifecycle changed with API 25 (7.1.1) - android

In my MainActivity, I have a dialog which is opened if a flag in the intent is set. If the dialog was created, it is dismissed in onPause()
#Override
public void onPause() {
super.onPause();
if (_dialog!= null) {
_dialog.dismiss();
_dialog= null;
}
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intentContainsFlag) {
_dialog = ....;
_dialog.show();
}
}
The dialog is to be opened if a ListView holder's button is pressed and builds an intent URI:
bttn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// The URL scheme is registered in the intent filter
String intentString = "http://open.example.com/myParameters";
v.getContext().startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(intentString)));
}
});
The AndroidManigfest contains:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:launchMode="singleTask"
android:screenOrientation="landscape" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:host="open.example.com" android:pathPattern=".*"/>
<data android:scheme="https" android:host="open.example.com" android:pathPattern=".*"/>
</intent-filter>
....
The sdk versions are set to
minSdkVersion = 19
targetSdkVersion= 22
compileSdkVersion = 23
buildToolsVersion = 23
On Android < 7.1.1, everything works as expected: onNewIntent() is called and the dialog is visible.
But on 7.1.1. devices
the MainActivity's onNewIntent is called, then directly afterwards onPause and onResume. This means that the activity opens itself / comes to the foreground but the dialog was immediately closed.
A possible workaround is to close the dialog in onStop() but I don't get why this happens on Android 7.1.1 - was something changed in the life cycle ?

But on 7.1.1. devices the MainActivity's onNewIntent is called, then directly afterwards onPause and onResume. This means that the activity opens itself / comes to the foreground but the dialog was immediately closed.
The Android framework may destroy your activity any time it's in the background or backstack, and you should write your activities so they behave correctly when this happens. look at this :
Don't keep activities under the Developer Options menu. When this
option is enabled, the Android OS will destroy an activity as soon as
it is stopped. It is intended to help developers debug their apps. For
example, it can simulate the case that Android will kill an activity
in the background due to memory pressure. In normal use, it is not
recommended to turn this option on because this may lead to unexpected
issues on the apps, such as freezes, force closes and reboots.
Your dialog itself causes your activity to be paused and than closed.

It seems that differents are not in the Android version.
If you enable "Don't keep activities" flag in developers settings, then lifecycle will be next:
onCreate
onResume
* perform startActivityForResult
onPause
onDestroy
* returning result
onCreate
onResume
onPause
onNewIntent
onResume
Because onNewIntent always comes in a paused state.

Related

Branch onInitFinished callback called twice if network is not accessible

I have empty Splash activity serving as entry point to the Android application and invoking appropriate activities if Branch data is received.
In case of Branch callback error or missing or unrecognized data it invokes default Main activity.
It all works well if device has Internet connectivity, but in case of failure onInitFinished callback is called twice in a row (once with empty data set and once triggering error), invoking Main activity twice.
public class SplashActivity extends AppCompatActivity
{
Branch.BranchReferralInitListener branchCallback;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.splash_activity);
Log.d("XAPP", "Splash");
branchCallback = new Branch.BranchReferralInitListener()
{
#Override
public void onInitFinished(JSONObject referringParams, BranchError error)
{
Log.d("XAPP", "Branch init session");
if (error == null)
{
Log.d("XAPP", referringParams.toString());
// run different activities depending on the parameters
....
else
{
// fallback to Main activity
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
startActivity(intent);
}
}
else
{
Log.i("XAPP", error.getMessage());
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
startActivity(intent);
}
finish();
}
};
}
#Override
protected void onStart()
{
super.onStart();
Log.d("XAPP", "onStart");
Branch branch = Branch.getInstance();
branch.initSession(branchCallback);
}
}
Resulting logcat when application runs without being connected to the Internet (after it has been previously - at some point - opened through Branch deep link and Branch data has been initialized):
D/XAPP: Splash
D/XAPP: onStart
D/XAPP: Branch init session
D/XAPP: {"+is_first_session":false,"+clicked_branch_link":false}
D/XAPP: Branch init session
I/XAPP: Trouble initializing Branch. Branch API Error: poor network connectivity. Please try again later.
Splash activity is declared as singleTask activity and is started only once.
Relevant parts of AndroidManifest:
<uses-permission android:name="android.permission.INTERNET"/>
...
<activity
android:name=".SplashActivity"
android:launchMode="singleTask"
android:label="#string/app_name"
android:theme="#style/AppTheme.FullScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Branch URI Scheme -->
<intent-filter>
<data android:host="open" android:scheme="xxxx"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:theme="#style/AppTheme.FullScreen">
</activity>
Relevant parts of the Gradle:
android {
compileSdkVersion 26
buildToolsVersion '27.0.3'
defaultConfig {
minSdkVersion 15
targetSdkVersion 26
...
dependencies {
compile('io.branch.sdk.android:library:2.14.4') {
exclude module: 'answers-shim'
}
I could solve the issue by making Main activity singleTask or removing the Splash activity altogether - by moving branching into Main activity, but those solutions are not viable options in this particular case.
One of the possible solutions would also be adding some boolean flag to recognize onInitFinished has already been called, but I would like to avoid that one if possible.
My main concern in this situation and actual question is not how to hack the thing to make it work, but why is onInitFinished called twice and is there a flaw in my Branch callback implementation?
I tested with your SplashActivity in a sample app and the callback was fired only once.
Here is my repo.
Please test with this app to check if you can replicate the behavior
In order to compile the app:
1. Add the URI scheme from your Branch dashboard to the Android Manifest
2. Add the Branch key for your app to the Manifest
3. Add your link domain to the app link filter in the Manifest.
Also, I would suggest upgrading the Branch SDK to the latest version i.e. 2.14.4.
If you have a slightly varied implementation, could you either share your Manifest file here. If not, you could also write into integrations#branch.io where the team could help you efficiently.
Single Task launch mode is required!
The reason for this is because if there is no singleTask Activity instance in the system yet, a new one would be created and simply placed on top of the stack in the same Task. If you are using the Single Task mode as is, it should not restart your entire app. The Single Task mode instantiates the Main/Splash Activity only if it does not exist in the Activity Stack. If the Activity exists in the background, every subsequent intent to the Activity just brings it to the foreground.

Android: Sharing Data and Receiving text from 3rd party app. Why doesn't my app receive new text?

Summary
I am attempting to get data sent from the user via the Share menu. In this case, I'll use the basic Android web browser to select text and then share it to my app.
Problem
The first time the user shares the text my app gets the text as expected and displays it via Log.d() -- see the handleSendText() method in the code below.
However, each time thereafter even though the user has selected new text in the web browser and shared it with my app, I still get the original text the user selected (previous value).
Question
How do you reset the Intent -- or whatever it is -- so that I can obtain the new text the user has selected after the first time?
Details
My application has a MainActivity and I've followed the Google docs at :
http://developer.android.com/training/sharing/receive.html
With code like the following in my MainActivity
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
if ("text/plain".equals(type)) {
handleSendText(intent, "onCreate"); // Handle text being sent
}
}
}
#Override
public void onResume(){
super.onResume();
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
if ("text/plain".equals(type)) {
handleSendText(intent, "onResume"); // Handle text being sent
}
}
}
void handleSendText(Intent intent, String callingMethodName) {
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (sharedText != null) {
Log.d("MainActivity", "sharedText : " + sharedText + " called from : " + callingMethodName);
}
}
}
My AndroidManifest section for the activity has the filter added like:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
Walk-Thru With Screens and Log
NOTE: Please notice that I've implemented the onResume() in my app also to insure that I don't only get the Intent when onCreate() is called (only one time when the app is started).
Start up browser and grab the text "hurricane".
Choose the app to share with (our test app).
View the log and notice that onCreate() and onResume() are called and value is 'hurricane'
Go back to browser again to share more text...
Select a new word, Atlantic, to share.
Extra note: When we click that Share link this time the Android MenuChooser doesn't display, instead, it automatically opens GrabText again. I found that behavior somewhat odd.
Notice that the Intent text still has the value of hurricane. You can see that there are now two new entries in the logcat.
Attempted Workaround Solutions
I have found that I can destroy the app entirely by overriding onPause() and calling finish() on my Activity (thus closing the entire app) and that seems to work, but isn't there some other way to reset that Intent or the associated text or something?
Do you know of any other way to insure that the new data is retrieved?
I appreciate any help.
UPDATE
Note:I'm updating because there's not a great way to show additional code tried, however, I wouldn't have know to try this without input from other SO User, CommonsWare.
The first answer I received was that I should add an #Override onNewIntent() so I added the following code to my MainActivity:
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d("MainActivity", "onNewIntent()...");
String action = intent.getAction();
String type = intent.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
if ("text/plain".equals(type)) {
handleSendText(intent, "onNewIntent"); // Handle text being sent
}
}
}
Upon adding that code and running and attempting the copy and then second copy of the new word, I still saw the following in logcat:
It doesn't even look as if the onNewItent() method is even called.
EDIT 2
I altered the emulator Settings...Developer Options... and turned off the "Don't keep activities" setting. It was previously turned on (checked).
After that, I ran the app which contains the onNewIntent() override but now it shows just the one onCreate() gone (which makes sense because the activity is still loaded) but still does not show the onNewIntent() call.
In this sample, I captured the word "remnants".
Edit 3
I built the app and created an APK and deployed it to my Samsung Galaxy Core Prime and I ended up with the same results. onNewIntent() is never called.
I just looked up onNewIntent in Google docs and it states:
onNewIntent(Intent intent) This is called for activities that set
launchMode to "singleTop" in their package, or if a client used the
FLAG_ACTIVITY_SINGLE_TOP flag when calling startActivity(Intent).
I haven't set singleTop so I will try it now. Hmm....
EDIT 4
I have now tried the singleTop variation. I was previously testing on API 15 (v4.0.4)on an emulator so I switched to API 21 (v5.0) to see if there'd be any different.
Here's what the addition of singleTop did to my AndroidManifest.xml:
<activity android:name=".MainActivity" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
Notice I also collapsed the two intent-filter nodes into the one.
Selected Text Did Change
On Android API Level 21 the Intent text was now coming in different each time I selected text in the browser.
onNewIntent Is Never Called
However, onNewIntent is NEVER called. I don't ever see it fire.
Share Menu Displayed Every Time
Also, now (on API 21) I see the Share menu every time I select text.
However, I also see an interesting thing when I switch to the browser. You can see multiple copies of the Activity in the list. What?!
Notice also that I implemented the MainActivity as a ListView (scrollable) so I could see the entries even without logcat (for running on real device). That made something else apparent: that the ListView was being updated on each newly shown Activity. But really, it should be the original Activity being appended to.
Creates Numerous GrabText Activities
Yes, now it creates a new GrabText Activity window each time I select text. I thought maybe that was because I had the singleTop set so I removed it but they still appear even after removing singleTop on API LEVEL 21.
Now that I saw it work -- provide different text each time on API 21 I decided to switch back to API Level 15 emulator and try it.
I will report back after I try some things back on API Level 15 again.
API Level 15 : Test Again
I started my other emulator running API Level 15 again and ran the app and even with singleTop set the value is never updated.
You can see this in the logcat and on the updated ListView:
You can also see that the code acts completely different, though I've not changed anything since it appends to the ListView of the one running Activity on api level 15.
I've written a book on this terribly documented thing. I hope this helps someone and that a Google Android dev sees this and explains it.
If your activity already exists, it will be called with onNewIntent() instead of onCreate(). onNewIntent() will be passed the Intent that you need to use for your message.
Try changing the android:launchMode of your activity in the manifest.xml to
singleTop
this way if the activity is already launched, new intents will be received in onNewIntent() method
There is only one answer that actually works, but it could cause other problems.
You just have to decide to call finish() whenever the Activity goes into onPause().
Here's the exact code I implemented which works on all API LEVELS.
#Override
public void onPause(){
super.onPause();
finish();
}
Destroy the Activity
When you add that code then every time you switch back to the app you are sharing from (the web browser in our case) then the onPause will fire on your MainActivity and the finish() method will set the Activity for destruction.
Share Menu Displayed Every Time After This
With this solution every time you select text from your sharing app (web browser) then the Share menu will be displayed and will display GrabText as one of the choices (instead of automatically forcing GrabText to the front again).
Shared Text Is Always the New Text
Since the MainActivity is completely destroyed it then has to be completely loaded (onCreate()) again and so it receives the new Intent text which was sent.
Not A Great Workaround
This isn't a great workaround however, because I believe dialog boxes in your app would also create onPause() to be called and your Activity would be destroyed. Obviously destroying your Activity onPause() just isn't great either because you are beginning to manage "memory" in a way that really should be left to the OS. However, in this case it seems to be the only way around the issue.

How to receive Intent when USB Accessory is attached if application is already running

I was able to read up here about how to launch my app when a USB accessory is attached, and that much all works fine: I have an IntentFilter built into my manifest which will launch the appropriate activity each time the specified accessory is attached. Here is how that looks:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="#xml/accessory_filter" />
</activity>
However, I am having issues if my Activity is already running when the accessory is attached. Within MainActivity, I look for the USB_ACCESSORY_ATTACHED Intent in two places: onCreate, and onNewIntent, like so:
protected void onCreate(Bundle savedInstanceState) {
//Check for some other actions first
if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(intent.getAction()))
usbAttached(intent);
}
protected void onNewIntent(Intent intent) {
if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(intent.getAction()))
usbAttached(intent);
}
However, neither onCreate nor onNewIntent is being called when the accessory is plugged in if MainActivity is already running. In that case, I need to close my app before plugging in the accessory, which would be a hassle for users. What would be the appropriate way to receive the Intent from my USB accessory if my activity is already running? Would I need to implement a separate listener within the activity itself?
If you look at the documentation of onNewIntent() it reads:
protected void onNewIntent (Intent intent)
Added in API level 1
This is called for activities that set launchMode to "singleTop" in their package, or if a client used the FLAG_ACTIVITY_SINGLE_TOP flag when calling startActivity(Intent). In either case, when the activity is re-launched while at the top of the activity stack instead of a new instance of the activity being started, onNewIntent() will be called on the existing instance with the Intent that was used to re-launch it.
...
So you need to set the launchMode of your Activity to singleTop in your manifest, otherwise you will receive no calls to onNewIntent(). The result should look something like this:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:launchMode="singleTop"
android:exported="true">
...
</activity>
If you think about it that behavior actually makes a lot of sense. Next time look at the documentation before asking here. Don't assume how stuff works if you don't know.
Write a BroadcastReceiver and register it in one your activity lifecycle methods via Context.registerReceiver(...). Don't forget to unregister it in the corresponding teardown lifecycle method.

Facebook deep linking on Android

I'm trying to implement Facebook's Deep Linking feature on my app and encountered the following scenario:
I have an activity called MainActivity which is declared like so:
<activity
android:name="com.mypackage.android.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
This activity + my package name are also declared in my app's settings on facebook developer website.
Once a link gets clicked on Facebook's app, I'm supposed to handle this event via the onCreate method of my activity.
The following code handle the event:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri target = getIntent().getData();
if (target != null){
// got here via Facebook deep link
// once I'm done parsing the URI and deciding
// which part of my app I should point the client to
// I fire an intent for a new activity and
// call finish() the current activity (MainActivity)
}else{
// activity was created in a normal fashion
}
}
All goes according to plan except for the following scenario:
User launched my app
MainActivity created
SecondaryActivity created
MainActivity finished
App goes to background via the device home button
Deep link gets clicked on Facebook's app
In this case my app goes to foreground again, but MainActivity's onCreate / onNewIntent
don't get called, instead SecondaryActivity's onResume() gets called and restored to it's
last state.
Note: I've tested this issue on a Samsung Nexus with Android 4.2.1 and got to this result, though when tested on Galaxy S1 with Android 2.3.5 it worked as I initially expected.
Any help would be greatly appreciated,
Thank you.
Facebook is starting your app from their own app by explicitly start your "MainActivity" (the one your provided them in the developer page).
by that - Android's default behavior is: if the application already runs, then calling again to startActivity() won't start new task from scratch, but only restore to foreground the already running task.
but the good news are that you can change this default behavior by adding to your MainActivity the android:launchMode="singleTask". it definition is:
the system creates a new task and instantiates the activity at the root of the new task. However, if an instance of the activity already exists in a separate task, the system routes the intent to the existing instance through a call to its onNewIntent() method, rather than creating a new instance. Only one instance of the activity can exist at a time.
from this point you could always respond to the starting intent, and from that point you can always navigate back to the task that already was in background(if exists) by restarting activity with both flags Intent.FLAG_ACTIVITY_SINGLE_TOP && Intent.FLAG_ACTIVITY_CLEAR_TOP combination
See http://developer.android.com/guide/topics/manifest/activity-element.html
You can play with:
android:clearTaskOnLaunch
android:noHistory
android:launchMode
You need to have more information in your intent filter:
<intent-filter>
<action android:name="android.intent.action.VIEW"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<category android:name="android.intent.category.BROWSABLE"></category>
<data android:host="www.yoursite.com" android:scheme="http"></data>
</intent-filter>
This will capture links going to your site (make sure to change the URL), and direct them to whatever Activity you define this intent filter under.

Issues with Translucent Theme

I have an app that has two activities.
The first one is presented with a single button that opens the second one.
Here is the Manifiest definition for the first one:
<activity
android:name="com.example.buttonexample.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Second activity:
<activity
android:name="com.example.buttonexample.MainActivity2"
android:label="#string/title_activity_main_activity2" android:theme="#android:style/Theme.Translucent">
</activity>
Here is how I launch the second activity (via OnClickListener for a button on the first activity):
public void startSecondActivityClick(View v) {
Intent startActivity2 = new Intent(this, MainActivity2.class);
startActivity(startActivity2);
}
This works fine, however when I background the app by hitting home and the foreground the app. I'm noticing that the first activity is continually creating/destroying itself. I verified this by putting some code in the onDestory method to increment a static int:
private static int count = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
count++;
}
protected void onDestroy() {
super.onDestroy();
Log.i("MainActivity", String.format("Destroyed, %d", count));
}
I've also noticed that removing the translucent theme seems to fix this. My question is is there a way to translucent or something similar but also have it not restart? Also, I'm curious why this happens at all. I'm testing this on 4.0.1 ICS on a galaxy SIII.
Ok after some digging I was able to figure out why this is happening. Someone had turned on one of the developer options, "do not keep activities.". After turning this off this stopped happening. I suspect this wouldn't happen in production too often as most people probably don't have that setting on. You can find this under settings -> "developer options" on most phones.

Categories

Resources