├── .github ├── renovate.json5 └── workflows │ ├── .java-version │ └── build.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── RELEASING.md ├── build.gradle ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── process-phoenix ├── build.gradle ├── gradle.properties └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── jakewharton │ └── processphoenix │ ├── PhoenixActivity.java │ ├── PhoenixService.java │ └── ProcessPhoenix.java ├── sample ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── jakewharton │ │ └── processphoenix │ │ └── sample │ │ ├── MainActivity.java │ │ ├── NotificationBuilder.java │ │ └── RestartService.java │ └── res │ └── layout │ └── activity_main.xml └── settings.gradle /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: 'https://docs.renovatebot.com/renovate-schema.json', 3 | extends: [ 4 | 'config:recommended', 5 | ], 6 | ignorePresets: [ 7 | // Ensure we get the latest version and are not pinned to old versions. 8 | 'workarounds:javaLTSVersions', 9 | ], 10 | customManagers: [ 11 | // Update .java-version file with the latest JDK version. 12 | { 13 | customType: 'regex', 14 | fileMatch: [ 15 | '\\.java-version$', 16 | ], 17 | matchStrings: [ 18 | '(?.*)\\n', 19 | ], 20 | datasourceTemplate: 'java-version', 21 | depNameTemplate: 'java', 22 | // Only write the major version. 23 | extractVersionTemplate: '^(?\\d+)', 24 | }, 25 | ], 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/.java-version: -------------------------------------------------------------------------------- 1 | 24 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | branches: 7 | - '**' 8 | tags-ignore: 9 | - '**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: gradle/actions/wrapper-validation@v4 18 | 19 | - uses: actions/setup-java@v4 20 | with: 21 | distribution: 'zulu' 22 | java-version-file: .github/workflows/.java-version 23 | 24 | - run: ./gradlew build 25 | 26 | - run: ./gradlew publish 27 | if: ${{ github.ref == 'refs/heads/trunk' && github.repository == 'JakeWharton/ProcessPhoenix' }} 28 | env: 29 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_CENTRAL_USERNAME }} 30 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_CENTRAL_PASSWORD }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ IDEA 2 | .idea 3 | *.iml 4 | 5 | # Gradle 6 | .gradle 7 | gradlew.bat 8 | build 9 | local.properties 10 | reports 11 | jacoco.exec 12 | 13 | .DS_Store 14 | 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | Version 3.0.0 *(2024-03-25)* 5 | ---------------------------- 6 | 7 | * New: Support restarting `Service`s with `triggerServiceRebirth`. 8 | * New: Restart an `Activity` with only a `Class` reference. 9 | * Change: `ProcessPheonix` class no longer extends `Activity`. In practice no one should have been relying on this anyway. 10 | 11 | 12 | Version 2.2.0 *(2024-02-26)* 13 | ---------------------------- 14 | 15 | * New: Support for launching the default Leanback intent on Android TV. 16 | * Strict mode's unsafe intent policy is now automatically disabled before launching back into the main application. 17 | 18 | 19 | Version 2.1.2 *(2021-08-13)* 20 | ---------------------------- 21 | 22 | * Fix: Add `exported=false` to activity for Android 12 targetSdk compatibility. 23 | 24 | 25 | Version 2.1.1 *(2021-06-25)* 26 | ---------------------------- 27 | 28 | * Fix: Ensure a new, clear task stack is used when user intent(s) are supplied. 29 | 30 | 31 | Version 2.1.0 *(2021-06-25)* 32 | ---------------------------- 33 | 34 | * Fix: Ensure default activity can be found on Android 8.0 and newer. 35 | * Fix: Reduce/eliminate launcher flashing when performing a process restart. 36 | * Fix: Useless `BuildConfig` class is no longer included. 37 | 38 | 39 | Version 2.0.0 *(2017-03-11)* 40 | ---------------------------- 41 | 42 | * New: Support for starting multiple intents at once. 43 | 44 | 45 | Version 1.1.1 *(2016-11-10)* 46 | ---------------------------- 47 | 48 | * Fix: Avoid potential NPE inside `isPhoenixProcess` method due to Android returning an unexpected 49 | `null` value. 50 | 51 | 52 | Version 1.1.0 *(2016-09-19)* 53 | ---------------------------- 54 | 55 | * New: `isPhoenixProcess` method checks whether the current process belongs to the library. Use this 56 | to skip any initialization you would otherwise do for a normal process. 57 | 58 | 59 | Version 1.0.2 *(2015-08-24)* 60 | ---------------------------- 61 | 62 | * Minimum SDK version is now set to 4. Sorry Cupcake users! 63 | 64 | 65 | Version 1.0.1 *(2015-07-31)* 66 | ---------------------------- 67 | 68 | * Fix: Finish the current activity when triggering rebirth which prevents it from remaining in the 69 | backstack if a diffrent activity was started. 70 | * Minimum SDK version is now set to 3. 71 | 72 | 73 | Version 1.0.0 *(2015-07-08)* 74 | ---------------------------- 75 | 76 | Initial import and release from [gist](https://gist.github.com/JakeWharton/9404647aa6a2b2818d22) 77 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Process Phoenix 2 | =============== 3 | 4 | Process Phoenix facilitates restarting your application process. 5 | 6 | This should only be used for things like fundamental state changes in your debug builds (e.g., 7 | changing from staging to production). 8 | 9 | 10 | 11 | Usage 12 | ----- 13 | 14 | Start the default activity in a new process: 15 | ```java 16 | ProcessPhoenix.triggerRebirth(context); 17 | ``` 18 | 19 | Or, if you want to launch with a specific `Intent`: 20 | ```java 21 | Intent nextIntent = //... 22 | ProcessPhoenix.triggerRebirth(context, nextIntent); 23 | ``` 24 | 25 | To check if your application is inside the Phoenix process to skip initialization in `onCreate`: 26 | ```java 27 | if (ProcessPhoenix.isPhoenixProcess(this)) { 28 | return; 29 | } 30 | ``` 31 | 32 | 33 | 34 | Download 35 | -------- 36 | 37 | ```groovy 38 | implementation 'com.jakewharton:process-phoenix:3.0.0' 39 | ``` 40 | 41 | Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. 42 | 43 | 44 | 45 | License 46 | ------- 47 | 48 | Copyright (C) 2015 Jake Wharton 49 | 50 | Licensed under the Apache License, Version 2.0 (the "License"); 51 | you may not use this file except in compliance with the License. 52 | You may obtain a copy of the License at 53 | 54 | http://www.apache.org/licenses/LICENSE-2.0 55 | 56 | Unless required by applicable law or agreed to in writing, software 57 | distributed under the License is distributed on an "AS IS" BASIS, 58 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 59 | See the License for the specific language governing permissions and 60 | limitations under the License. 61 | 62 | 63 | 64 | 65 | [snap]: https://central.sonatype.com/repository/maven-snapshots/ 66 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | Releasing 2 | ======== 3 | 4 | 1. Change the version in `gradle.properties` to a non-SNAPSHOT version. 5 | 2. Update the `CHANGELOG.md` for the impending release. 6 | 3. Update the `README.md` with the new version. 7 | 4. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version) 8 | 5. `git tag -a X.Y.X -m "Version X.Y.Z"` (where X.Y.Z is the new version) 9 | 6. `./gradlew clean publish` 10 | 7. Update the `gradle.properties` to the next SNAPSHOT version. 11 | 8. `git commit -am "Prepare next development version."` 12 | 9. `git push && git push --tags` 13 | 10. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact. 14 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.BaseExtension 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | google() 7 | gradlePluginPortal() 8 | } 9 | dependencies { 10 | classpath libs.androidPlugin 11 | classpath libs.gradleMavenPublishPlugin 12 | classpath libs.spotlessPlugin 13 | } 14 | } 15 | 16 | subprojects { 17 | repositories { 18 | mavenCentral() 19 | google() 20 | } 21 | 22 | tasks.withType(BaseExtension).configureEach { 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_7 25 | targetCompatibility JavaVersion.VERSION_1_7 26 | } 27 | } 28 | 29 | apply plugin: 'com.diffplug.spotless' 30 | spotless { 31 | java { 32 | googleJavaFormat(libs.googleJavaFormat.get().version) 33 | .formatJavadoc(false) 34 | removeUnusedImports() 35 | target 'src/**/*.java' 36 | } 37 | } 38 | } 39 | 40 | ext { 41 | minSdkVersion = 11 42 | compileSdkVersion = 34 43 | } 44 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | GROUP=com.jakewharton 2 | VERSION_NAME=3.1.0-SNAPSHOT 3 | 4 | POM_DESCRIPTION=A special activity which facilitates restarting your application process. 5 | 6 | POM_URL=https://github.com/JakeWharton/ProcessPhoenix/ 7 | POM_SCM_URL=https://github.com/JakeWharton/ProcessPhoenix/ 8 | POM_SCM_CONNECTION=scm:git:git://github.com/JakeWharton/ProcessPhoenix.git 9 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/JakeWharton/ProcessPhoenix.git 10 | 11 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 12 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 13 | POM_LICENCE_DIST=repo 14 | 15 | POM_DEVELOPER_ID=jakewharton 16 | POM_DEVELOPER_NAME=Jake Wharton 17 | 18 | SONATYPE_AUTOMATIC_RELEASE=true 19 | SONATYPE_HOST=CENTRAL_PORTAL 20 | RELEASE_SIGNING_ENABLED=true 21 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [libraries] 2 | androidPlugin = "com.android.tools.build:gradle:8.10.1" 3 | gradleMavenPublishPlugin = "com.vanniktech:gradle-maven-publish-plugin:0.32.0" 4 | spotlessPlugin = "com.diffplug.spotless:spotless-plugin-gradle:7.0.4" 5 | 6 | googleJavaFormat = "com.google.googlejavaformat:google-java-format:1.27.0" 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/ProcessPhoenix/b962e0f484ef37187e125b4b568d39523f1142e9/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /process-phoenix/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.vanniktech.maven.publish' 3 | 4 | android { 5 | namespace 'com.jakewharton.processphoenix' 6 | 7 | defaultConfig { 8 | minSdk rootProject.ext.minSdkVersion 9 | compileSdk rootProject.ext.compileSdkVersion 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /process-phoenix/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=process-phoenix 2 | POM_NAME=Process Phoenix 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /process-phoenix/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /process-phoenix/src/main/java/com/jakewharton/processphoenix/PhoenixActivity.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.processphoenix; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.os.Process; 8 | import android.os.StrictMode; 9 | 10 | public final class PhoenixActivity extends Activity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | 16 | // Kill original main process 17 | Process.killProcess(getIntent().getIntExtra(ProcessPhoenix.KEY_MAIN_PROCESS_PID, -1)); 18 | 19 | Intent[] intents = 20 | getIntent() 21 | .getParcelableArrayListExtra(ProcessPhoenix.KEY_RESTART_INTENTS) 22 | .toArray(new Intent[0]); 23 | 24 | if (Build.VERSION.SDK_INT > 31) { 25 | // Disable strict mode complaining about out-of-process intents. Normally you save and restore 26 | // the original policy, but this process will die almost immediately after the offending call. 27 | StrictMode.setVmPolicy( 28 | new StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy()) 29 | .permitUnsafeIntentLaunch() 30 | .build()); 31 | } 32 | 33 | startActivities(intents); 34 | finish(); 35 | Runtime.getRuntime().exit(0); // Kill kill kill! 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /process-phoenix/src/main/java/com/jakewharton/processphoenix/PhoenixService.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.processphoenix; 2 | 3 | import android.app.IntentService; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.os.Process; 7 | import android.os.StrictMode; 8 | 9 | /** 10 | * Please note that restarting a Service multiple times can result in an increasingly long delay between restart times. 11 | * This is a safety mechanism, since Android registers the restart of this service as a crashed service. 12 | *

13 | * The observed delay periods are: 1s, 4s, 16s, 64s, 256s, 1024s. (on an Android 11 device) 14 | * Which seems to follow this pattern: 4^x, with x being the restart attempt minus 1. 15 | */ 16 | public final class PhoenixService extends IntentService { 17 | 18 | public PhoenixService() { 19 | super("PhoenixService"); 20 | } 21 | 22 | @Override 23 | protected void onHandleIntent(Intent intent) { 24 | if (intent == null) { 25 | return; 26 | } 27 | 28 | Process.killProcess( 29 | intent.getIntExtra(ProcessPhoenix.KEY_MAIN_PROCESS_PID, -1)); // Kill original main process 30 | 31 | Intent nextIntent; 32 | if (Build.VERSION.SDK_INT >= 33) { 33 | nextIntent = intent.getParcelableExtra(ProcessPhoenix.KEY_RESTART_INTENT, Intent.class); 34 | } else { 35 | nextIntent = intent.getParcelableExtra(ProcessPhoenix.KEY_RESTART_INTENT); 36 | } 37 | 38 | if (Build.VERSION.SDK_INT > 31) { 39 | // Disable strict mode complaining about out-of-process intents. Normally you save and restore 40 | // the original policy, but this process will die almost immediately after the offending call. 41 | StrictMode.setVmPolicy( 42 | new StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy()) 43 | .permitUnsafeIntentLaunch() 44 | .build()); 45 | } 46 | 47 | if (Build.VERSION.SDK_INT >= 26) { 48 | startForegroundService(nextIntent); 49 | } else { 50 | startService(nextIntent); 51 | } 52 | 53 | Runtime.getRuntime().exit(0); // Kill kill kill! 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /process-phoenix/src/main/java/com/jakewharton/processphoenix/ProcessPhoenix.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Jake Wharton 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.jakewharton.processphoenix; 17 | 18 | import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; 19 | import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 20 | import static android.content.pm.PackageManager.FEATURE_LEANBACK; 21 | 22 | import android.app.Activity; 23 | import android.app.ActivityManager; 24 | import android.app.Service; 25 | import android.content.Context; 26 | import android.content.Intent; 27 | import android.content.pm.PackageManager; 28 | import android.os.Build; 29 | import android.os.Process; 30 | import java.util.ArrayList; 31 | import java.util.Arrays; 32 | import java.util.List; 33 | 34 | /** 35 | * Process Phoenix facilitates restarting your application process. This should only be used for 36 | * things like fundamental state changes in your debug builds (e.g., changing from staging to 37 | * production). 38 | *

39 | * Trigger process recreation by calling {@link #triggerRebirth} with a {@link Context} instance. 40 | */ 41 | public final class ProcessPhoenix { 42 | static final String KEY_RESTART_INTENT = "phoenix_restart_intent"; 43 | static final String KEY_RESTART_INTENTS = "phoenix_restart_intents"; 44 | static final String KEY_MAIN_PROCESS_PID = "phoenix_main_process_pid"; 45 | 46 | /** 47 | * Call to restart the application process using the {@linkplain Intent#CATEGORY_DEFAULT default} 48 | * activity as an intent. 49 | *

50 | * Behavior of the current process after invoking this method is undefined. 51 | */ 52 | public static void triggerRebirth(Context context) { 53 | triggerRebirth(context, getRestartIntent(context)); 54 | } 55 | 56 | /** 57 | * Call to restart the application process using the provided Activity Class. 58 | *

59 | * Behavior of the current process after invoking this method is undefined. 60 | */ 61 | public static void triggerRebirth(Context context, Class targetClass) { 62 | Intent nextIntent = new Intent(context, targetClass); 63 | triggerRebirth(context, nextIntent); 64 | } 65 | 66 | /** 67 | * Call to restart the application process using the specified intents. 68 | *

69 | * Behavior of the current process after invoking this method is undefined. 70 | */ 71 | public static void triggerRebirth(Context context, Intent... nextIntents) { 72 | if (nextIntents.length < 1) { 73 | throw new IllegalArgumentException("intents cannot be empty"); 74 | } 75 | // create a new task for the first activity. 76 | nextIntents[0].addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); 77 | 78 | Intent intent = new Intent(context, PhoenixActivity.class); 79 | intent.addFlags(FLAG_ACTIVITY_NEW_TASK); // In case we are called with non-Activity context. 80 | intent.putParcelableArrayListExtra( 81 | KEY_RESTART_INTENTS, new ArrayList<>(Arrays.asList(nextIntents))); 82 | intent.putExtra(KEY_MAIN_PROCESS_PID, Process.myPid()); 83 | context.startActivity(intent); 84 | } 85 | 86 | /** 87 | * Call to restart the application process using the provided Service Class. 88 | *

89 | * Behavior of the current process after invoking this method is undefined. 90 | */ 91 | public static void triggerServiceRebirth(Context context, Class targetClass) { 92 | Intent nextIntent = new Intent(context, targetClass); 93 | triggerServiceRebirth(context, nextIntent); 94 | } 95 | 96 | /** 97 | * Call to restart the application process using the specified Service intent. 98 | *

99 | * Behavior of the current process after invoking this method is undefined. 100 | */ 101 | public static void triggerServiceRebirth(Context context, Intent nextIntent) { 102 | Intent intent = new Intent(context, PhoenixService.class); 103 | intent.putExtra(KEY_RESTART_INTENT, nextIntent); 104 | intent.putExtra(KEY_MAIN_PROCESS_PID, Process.myPid()); 105 | context.startService(intent); 106 | } 107 | 108 | private static Intent getRestartIntent(Context context) { 109 | String packageName = context.getPackageName(); 110 | 111 | Intent defaultIntent = null; 112 | PackageManager packageManager = context.getPackageManager(); 113 | if (Build.VERSION.SDK_INT >= 21 && packageManager.hasSystemFeature(FEATURE_LEANBACK)) { 114 | // Use leanback intent if available, for Android TV apps. 115 | defaultIntent = packageManager.getLeanbackLaunchIntentForPackage(packageName); 116 | } 117 | if (defaultIntent == null) { 118 | defaultIntent = packageManager.getLaunchIntentForPackage(packageName); 119 | } 120 | if (defaultIntent != null) { 121 | return defaultIntent; 122 | } 123 | 124 | throw new IllegalStateException( 125 | "Unable to determine default activity for " 126 | + packageName 127 | + ". Does an activity specify the DEFAULT category in its intent filter?"); 128 | } 129 | 130 | /** 131 | * Checks if the current process is a temporary Phoenix Process. 132 | * This can be used to avoid initialisation of unused resources or to prevent running code that 133 | * is not multi-process ready. 134 | * 135 | * @return true if the current process is a temporary Phoenix Process 136 | */ 137 | public static boolean isPhoenixProcess(Context context) { 138 | int currentPid = Process.myPid(); 139 | ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 140 | List runningProcesses = manager.getRunningAppProcesses(); 141 | if (runningProcesses != null) { 142 | for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { 143 | if (processInfo.pid == currentPid && processInfo.processName.endsWith(":phoenix")) { 144 | return true; 145 | } 146 | } 147 | } 148 | return false; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | namespace 'com.jakewharton.processphoenix.sample' 5 | 6 | defaultConfig { 7 | minSdk rootProject.ext.minSdkVersion 8 | targetSdk rootProject.ext.compileSdkVersion 9 | compileSdk rootProject.ext.compileSdkVersion 10 | versionCode 1 11 | versionName "$VERSION_NAME" 12 | } 13 | } 14 | 15 | dependencies { 16 | implementation project(':process-phoenix') 17 | } 18 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /sample/src/main/java/com/jakewharton/processphoenix/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.processphoenix.sample; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.os.Process; 8 | import android.view.View; 9 | import android.widget.TextView; 10 | import com.jakewharton.processphoenix.ProcessPhoenix; 11 | 12 | public final class MainActivity extends Activity { 13 | private static final String EXTRA_TEXT = "text"; 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | 19 | setContentView(R.layout.activity_main); 20 | 21 | TextView processIdView = findViewById(R.id.process_id); 22 | TextView extraTextView = findViewById(R.id.extra_text); 23 | View restartButton = findViewById(R.id.restart); 24 | View restartWithIntentButton = findViewById(R.id.restart_with_intent); 25 | View restartActivityButton = findViewById(R.id.restartActivity); 26 | View restartServiceButton = findViewById(R.id.restart_service); 27 | 28 | processIdView.setText("Process ID: " + Process.myPid()); 29 | extraTextView.setText("Extra Text: " + getIntent().getStringExtra(EXTRA_TEXT)); 30 | 31 | restartButton.setOnClickListener( 32 | new View.OnClickListener() { 33 | @Override 34 | public void onClick(View v) { 35 | ProcessPhoenix.triggerRebirth(MainActivity.this); 36 | } 37 | }); 38 | 39 | restartWithIntentButton.setOnClickListener( 40 | new View.OnClickListener() { 41 | @Override 42 | public void onClick(View v) { 43 | Intent nextIntent = new Intent(MainActivity.this, MainActivity.class); 44 | nextIntent.putExtra(EXTRA_TEXT, "Hello!"); 45 | ProcessPhoenix.triggerRebirth(MainActivity.this, nextIntent); 46 | } 47 | }); 48 | 49 | restartActivityButton.setOnClickListener( 50 | new View.OnClickListener() { 51 | @Override 52 | public void onClick(View v) { 53 | ProcessPhoenix.triggerRebirth(MainActivity.this, MainActivity.class); 54 | } 55 | }); 56 | 57 | restartServiceButton.setOnClickListener( 58 | new View.OnClickListener() { 59 | @Override 60 | public void onClick(View v) { 61 | // Start the RestartService, which initiates the service restart cycle. 62 | // TODO: Request permissions when the API level is high enough 63 | // to require FOREGROUND_SERVICE or POST_NOTIFICATIONS 64 | Intent restartServiceIntent = new Intent(MainActivity.this, RestartService.class); 65 | if (Build.VERSION.SDK_INT >= 26) { 66 | startForegroundService(restartServiceIntent); 67 | } else { 68 | startService(restartServiceIntent); 69 | } 70 | finish(); 71 | } 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /sample/src/main/java/com/jakewharton/processphoenix/sample/NotificationBuilder.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.processphoenix.sample; 2 | 3 | import android.Manifest; 4 | import android.annotation.TargetApi; 5 | import android.app.Notification; 6 | import android.app.NotificationChannel; 7 | import android.app.NotificationManager; 8 | import android.content.Context; 9 | import android.content.pm.PackageManager; 10 | import android.os.Build; 11 | import android.util.Log; 12 | 13 | public final class NotificationBuilder { 14 | 15 | /** 16 | * Create a Notification, required to support Service restarting on Android 8 and newer 17 | */ 18 | @TargetApi(26) 19 | public static Notification createNotification(Context context) { 20 | // Android 13 or higher requires a permission to post Notifications 21 | if (Build.VERSION.SDK_INT >= 33) { 22 | if (context.checkCallingOrSelfPermission(Manifest.permission.POST_NOTIFICATIONS) 23 | != PackageManager.PERMISSION_GRANTED) { 24 | Log.e( 25 | "ProcessPhoenix", 26 | "Required POST_NOTIFICATIONS permission was not granted, cannot restart Service"); 27 | return null; 28 | } 29 | } 30 | 31 | // Android 8 or higher requires a Notification Channel 32 | if (Build.VERSION.SDK_INT >= 26) { 33 | // Creating an existing notification channel with its original values performs no operation, 34 | // so it's safe to call this code multiple times 35 | NotificationChannel channel = 36 | new NotificationChannel( 37 | "ProcessPhoenix", "ProcessPhoenix", NotificationManager.IMPORTANCE_NONE); 38 | 39 | // Create Notification Channel 40 | NotificationManager notificationManager = 41 | (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 42 | notificationManager.createNotificationChannel(channel); 43 | } 44 | 45 | // Create a Notification 46 | return new Notification.Builder(context, "ProcessPhoenix") 47 | .setSmallIcon(android.R.mipmap.sym_def_app_icon) 48 | .setContentTitle("ProcessPhoenix") 49 | .setContentText("PhoenixService") 50 | .build(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /sample/src/main/java/com/jakewharton/processphoenix/sample/RestartService.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.processphoenix.sample; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.IntentService; 5 | import android.content.Intent; 6 | import android.os.Build; 7 | import android.os.Process; 8 | import android.os.SystemClock; 9 | import android.util.Log; 10 | import com.jakewharton.processphoenix.ProcessPhoenix; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | 14 | /** 15 | * This example service will attempt to restart after 1 second 16 | *

17 | * Please note that restarting a Service multiple times can result in an increasingly long delay between restart times. 18 | * This is a safety mechanism, since Android registers the restart of this service as a crashed service. 19 | *

20 | * The observed delay periods are: 1s, 4s, 16s, 64s, 256s, 1024s. (on an Android 11 device) 21 | * Which seems to follow this pattern: 4^x, with x being the restart attempt minus 1. 22 | */ 23 | public final class RestartService extends IntentService { 24 | 25 | public RestartService() { 26 | super("RestartService"); 27 | } 28 | 29 | @SuppressLint("ForegroundServiceType") 30 | @Override 31 | protected void onHandleIntent(Intent intent) { 32 | // Log something to console to easily track successful restarts 33 | Log.d("ProcessPhoenix", "--- RestartService started with PID: " + Process.myPid() + " ---"); 34 | 35 | if (Build.VERSION.SDK_INT >= 26) { 36 | startForeground(1337, NotificationBuilder.createNotification(RestartService.this)); 37 | } 38 | 39 | // Trigger rebirth from a separate thread, such that the onStartCommand can finish properly 40 | ExecutorService executorService = Executors.newSingleThreadExecutor(); 41 | executorService.execute( 42 | () -> { 43 | SystemClock.sleep(1000); 44 | ProcessPhoenix.triggerServiceRebirth(RestartService.this, RestartService.class); 45 | // ProcessPhoenix.triggerServiceRebirth(RestartService.this, new 46 | // Intent(RestartService.this, RestartService.class)); 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | 19 | 27 | 28 |