I would like you to follow these lecture notes with an Android project for Greeter open, editing and re-running the code as you read. The code and XML files are in examples/Greeter1a/. You will need to recreate the project and paste them to appropriate places. ==================[ The Backstack of Activities ]====================== Activities Backstack: when an Activity starts another Activity by sending it an Intent, the _default_ behavior is for the calling Activity to be completely obscured by the callee. As Activities start each other, the system keeps track of them in the form of a _stack_. Hitting the "Back" button would pop the top activity off the stack, revealing the previous one. There are other non-default behaviors that can be configured in XML and by flags passed to Java functions that start Activities. Read https://developer.android.com/guide/components/activities/tasks-and-back-stack.html to understand this model and the special behaviors such as _singleTop_, _singleTask_, and _singleInstance_. Think about examples where each behavior is desired. ======================[ How the Backstack works ]====================== The caller, though obscured, does not get _destroyed_; only _paused_ and _stopped_. You can observe this by overriding the lifecycle callbacks: you will see that onPause() and onStop() are called before the new activity gets its onCreate(..) call. The difference between Pause and Stop is in what behaviors of the activity being paused/stopped are curtailed. A _paused_ (caller) activity may still be visible (if the new activity is not fully opaque), but its Views not longer receive touches and clicks from the screen, which is now taken over the Views of the callee. A _stopped_ activity does not have visible views, but still retains its full state (unlike an activity that goes through onDestroy(), which only gets to save what is passed in a savedInstanceState Bundle; see below). The obscured, stopped caller will be shown again, passing through onStart() and onResume(). Note from the Greeter example that onActivityResult() in the caller gets called first, then onStart() and onResume(). onActivityResult() and the restoration of the caller Views can happen in two ways. One is the callee's finish(). In that case, onActivityResult() should get RESULT_OK for its resultCode argument, and a non-null Intent object set by the caller with setResult() prior to finish(). The second way is by the caller activity being destroyed with the Back button. In that case, resultCode should be RESULT_CANCELED and the Intent object argument will be null. Remove the check for null around (*) in @Override protected void onActivityResult(int requestCode, int resultCode, Intent data){ ... Log.d("RESULT INTENT", data.toString()); // (*) ... } and see the crash. =============[ Activity Lifecycle and the Backstack ]============== I instrumented the class example of the Greeter application with log messages in lifecycle functions of MainActivity. Add them to EditActivity as well, and observe the sequence of events and callbacks as you press "Back", "Home", etc., in different screens. Follow the diagram in https://developer.android.com/guide/components/activities/activity-lifecycle.html to make sense of the series of events. ==============[ Seeing the Backstack ]============== Observing the backstack for all processes is possible with the _adb_ debugger. Although the text dump format is not the best to read, the adb shell dumpsys gfxinfo shows the stacks for all running apps, alongside with much other info, mostly performance-related. You'll see later in the course that profiling performance of Adnroid apps is a hard battle, because you deal with a Linux system underneath, a virtual machine on top, and many threads inside it. iPhone has an easier time of it, having purpose-designed iOS and ObjectiveC (and now Swift, which you could compare with Kotlin). Make sure ADB is in your path. On my machine, I run export PATH=$PATH:/Users/sergey/Library/Android/sdk/platform-tools and then adb is in the path. Sample output: ----------------------[ begin quote ]---------------------- Applications Graphics Acceleration Info: Uptime: 176031291 Realtime: 176031291 ** Graphics info for pid 31548 [com.netfluke.sergey.greeter] ** Stats since: 171981785486040ns Total frames rendered: 69 Janky frames: 16 (23.19%) Number Slow UI thread: 11 Number Slow bitmap uploads: 0 Number Slow issue draw commands: 10 HISTOGRAM: 5ms=0 6ms=0 7ms=5 8ms=15 9ms=15 10ms=3 11ms=7 12ms=3 13ms=2 14ms=1 15ms=2 16ms=0 17ms=1 18ms=0 19ms=0 20ms=2 21ms=0 22ms=0 23ms=1 Caches: Current memory usage / total memory usage (bytes): /////////// OK, this is it, the backstack, bottom to top: ///////////// com.netfluke.sergey.greeter/com.netfluke.sergey.greeter.MainActivity/android.view.ViewRootImpl@f34d6c3 (visibility=8) com.netfluke.sergey.greeter/com.netfluke.sergey.greeter.EditActivity/android.view.ViewRootImpl@c10b40 (visibility=0) ----------------------[ end quote ]---------------------- ======================[ Filtering Intents ]====================== Recall from the previous lecture notes and assigned reading from the Developer Guide that Intents come in two flavors: _explicit_ (directly naming the Java class to start for the new Activity; this class is also called "component"), and _implicit_ (specifying _action_, _category_, and _type_, which are all strings: arbitrary names, URIs such as class names, and, in case if _type_, MIME types such as "text/plain", "text/html", "image/gif", etc.) If an Intent fails to be resolved (i.e., it cannot be delivered to any class on the system), then the Android system throws an exception. Unless you use a try/catch block, that exception will be fatal: if the system cannot complete your requested action, and you have no provisions to catch the failure, then you app probably cannot continue and must stop. It's a design choice. What makes Intents succeed or fail? For an explicit intent to succeed, some application on the Android system should have the target class in its Manifest. The intent will be routed to the Activity, Service, BroadcastReceiver, etc. that is implemented by that class. For an implicit intent to succeed, some application must declare a matching element in its Manifest. The matching logic is AND: all element-clauses in an must match for the Intent to be accepted. There are other restrictions besides. For example, action MUST be present in a filter, and receiving implicit intents require that you include CATEGORY_DEFAULT in the filter. See https://developer.android.com/guide/topics/manifest/intent-filter-element.html https://developer.android.com/guide/topics/manifest/category-element.html (and https://developer.android.com/guide/components/intents-filters.html if you haven't read it yet). To express the logic of OR, i.e., to receive several kinds of intents, you just need to add another . Their contents are AND-ed, but these elements themselves are OR-ed. TRY THIS: remove intent filters and their clauses from the Manifest of Greeter and observe what breaks. NOTE: An activity without any s can only be reached by explicit intents that target its exact class. ======================[ savedInstanceState Bundles ]====================== A Bundle is kind of a dictionary, where objects (of any type) can be looked up by keys that are strings. Python calls them dictionaries, Javascript calls then hash tables, and so do Perl and Ruby. The point is, you can pass arbitrary objects in Bundles. Bundles are used to carry the "extras" data of Intents (by default, an Intent just carries a URI string, but can include any number of extras, e.g., images). Look to the textbook for examples of this. Bundles are also used directly to save and restore the state of an Activity as it passes through onDestroy()/onCreate(..). Note the "Bundle savedInstanceState" argument to onCreate(..), the only lifecycle function with an argument. The purpose of this argument is to restore the contents of an Activity that has been killed by the "Back" button or by an orientation flip. Log the value of this Bundle converted to String: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //... Log.d("BUNDLE onStart", savedInstanceState.toString()); //... } You will see that on the first invocation of the Activity it is null, but all following ones (so long as the application is running) will save and restore the values of _all named View components_ such as TextEdit, so long as these components have an Android ID (e.g., "android:id='@+id/somename"). In your activities, this is taken care of by the super.onCreate(savedInstanceState) and onRestoreInstanceState(Bundle) for restoring, and by onSaveInstanceState(Bundle) for saving. If you need to save some other non-GUI state, you should add saving it to onPause() and recover it in onResume(). See discussion in https://stackoverflow.com/questions/151777/saving-android-activity-state-using-save-instance-state . Note than you will need to use something other than Bundles in onPause()/onResume(), like a file or a SQL database (SQLite is available on Android). We will see simple examples of this later in the class. ========[ Sending Intents to a backgrounded Activity ]========== At the end of the Friday class, a student asked: can I send an Intent from an activity currently on the screen (i.e., on top of the backstack) to an activity that is currently obscured? The answer is yes. There is a mechanism for activities to send and receive Intents in flows that are more complex than the default workflow startActivityForResult(..) -> [ finish() or back button in the callee] -> -> onActivityResult(..) Of course, you should first ask if you need another entry point in your activity to manage in your code. My in-class example, I use the press of a button to send a message to the backgrounded MainActivity from the currently running EditActivity, which changes an internal variable within MainActivity. There's no need for this convoluted design other than to show that we can do it. You need to register a BroadcastReceiver in the receiving activity, e.g.: IntentFilter filter = new IntentFilter(); filter.addAction("android.intent.action.DR"); registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.d("RCV", intent.toString()); pressed = true; } }, filter); To send such an Intent, an implicit intent called the broadcast intent, you just need to Intent i = new Intent(); i.setAction("android.intent.action.DR"); sendBroadcast(i); So long as the implicit intent matches the receiver, you can call code in the context of the backgrounded activity, without finishing your current activity. See your textbook on how BroadcastReceiver is used. ======================[ APKtool ]====================== Given a binary APK, you may want to recover its manifest, layouts, and other resources. You cannot recover Java code after it's been compiled, but you want, at least, to see what methods are called with what arguments---that can help a lot when learning an API. Even though APKs are really ZIP archives, the relevant XML is stored in a binary format, which is unreadable and essentially undocumented. However, there are tools to recover the readable XML. This will become important when we start looking at Layouts, and you'd want to find out how this or that app handles its layout. APKtool is just such a tool. It allows you to unpack the APK, and, with luck, to even repack it after some changes! See https://ibotpeaches.github.io/Apktool/documentation/ and get it at https://ibotpeaches.github.io/Apktool/ . I run it with "java -jar apktool_2.2.4.jar d MyRuns-Android-chk1.apk" for a demo app in a previous edition of this course, http://www.cs.dartmouth.edu/~campbell/cs65/myruns/apk/MyRuns-Android-chk1.apk to see its AndroidManifest.xml and its res/layout/ layouts. This tool also produces disassembly of the Java classes, in the assembly language called Smali. From these disassembly files for each class, you can guess which APIs were called and where.