Android 直接通過 app_process 啟動的應用如何使用 Context

文章目錄

  • 一、問題背景
  • 二、代碼實現
  • 三、代碼詳解

一、問題背景

Android 中,可以使用 Android Studio 編寫 Java 應用程序,通過編譯打包成 apk 文件,然后將文件推送至 /data/local/tmp 等可執行的目錄或安裝打包出來的應用,隨后使用 app_process 命令即可運行此 Java 程序,命令格式如下:

app_process -Djava.class.path=${apk路徑} /system/bin 主類的全限定名

例如如下:
Android Studio 中編寫一個 Hello World 程序:

package com.teleostnaclobject HelloWorld {@JvmStaticfun main(args: Array<String>) {println("Hello World!!!")}
}

編譯完成之后,推送至 /data/local/tmp,隨后使用命令執行 app_process -Djava.class.path="/data/local/tmp/HelloWorld.apk" /system/bin com.teleostnacl.HelloWorld
在這里插入圖片描述
可以看到其可以正常執行 Java 代碼并輸出 Hello World!!!
這個方式啟動的 Java 程序將集成 shell用戶組用戶,可以執行高權限的命令,實現應用的提權,流行于各種工具箱中。但是由于這樣啟動的 Java 程序是沒有Application Activity 和其它四大組件的,無法直接拿到 Context,而對于很多 Android API 來說都需要用到 Context 的。那么有沒有辦法去拿到一個 Context 呢?本文將介紹一種可以在通過 app_process 命令啟動的 Java 程序中獲取到 Context 的方式。

二、代碼實現

先直接上代碼,看如何在代碼中構造一個 Context

val context: Context? by lazy {try {Looper.prepareMainLooper()// ActivityThread activityThread = new ActivityThread();val activityThreadCls = Class.forName("android.app.ActivityThread")val activityThreadConstructor: Constructor<*> = activityThreadCls.getDeclaredConstructor()activityThreadConstructor.isAccessible = trueval activityThread = activityThreadConstructor.newInstance()val getSystemContextMethod = activityThreadCls.getDeclaredMethod("getSystemContext")getSystemContextMethod.invoke(activityThread) as Context} catch (e: Exception) {e.printStackTrace()null}
}

參考:scrcpy 的 Workarounds.java

此方法的原理是通過 ActivityThread.getSystemContext() 來構造獲取 Context,但是由于 ActivityThread 的構造方法和 getSystemContext() 方法都是被打上了 @UnsupportedAppUsage 注解的,外部無非直接調用,因此需要通過反射來構造。
在這里插入圖片描述

三、代碼詳解

我們可以通過分析 Android App 的啟動流程來了解到 Context 被構造出來的過程。
我們知道 Android App 是基于 Java 編寫的一種特殊的程序,其跟 Java 程序一樣,需要通過 main 方法來作為應用程序的唯一入口。在 APP 啟動時,會先通過 AMS 請求啟動應用,通過 Zygote 使用 fork() 方法復制自身,創建新的應用進程,此時會加載 Android Runtime (ART) 進行加載 Java 虛擬機和核心庫,再調用 ActivityThreadmain() 方法,這就是應用進程的入口點。
我們來看一下 ActivityThreadmain() 實際的實現:

public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// Install selective syscall interceptionAndroidOs.install();// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);// Call per-process mainline module initialization.initializeMainlineModules();Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.// It will be in the format "seq=114"long startSeq = 0;if (args != null) {for (int i = args.length - 1; i >= 0; --i) {if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {startSeq = Long.parseLong(args[i].substring(PROC_START_SEQ_IDENT.length()));}}}ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");
}

在這段代碼中,前幾行都是為了初始化相關的模塊,以便后續在 Android 應用中可以正常使用相關的功能,隨后 調用 Looper.prepareMainLooper(); 將主線程的 Looper 給準備好,隨后

    ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);

構建了一個 ActivityThread,并調用 attach 方法將其綁定。最后調用 Looper.loop(); 開始循環處理主線程的消息。
我們再來看 attach 方法:
在這里插入圖片描述
這個方法中的邏輯較多,核心的就是給 sCurrentActivityThreadmSystemThread 進行賦值,并將ApplicationThread attachAMS 中,向 AMS 注冊應用進程。最后添加與 View 有關的配置修改的回調。

我們再看與 Context 有關的邏輯:

@UnsupportedAppUsage
ActivityThread() {mResourcesManager = ResourcesManager.getInstance();
}@Override
@UnsupportedAppUsage
public ContextImpl getSystemContext() {synchronized (this) {if (mSystemContext == null) {mSystemContext = ContextImpl.createSystemContext(this);}return mSystemContext;}
}

可以看到 getSystemContext() 就是一個獲取 Context 的關鍵方法,而在其內部調用 ContextImpl.createSystemContext(); 方法,并傳遞了一個 ActivityThread 的實例。
ActivityThread 的構造方法和 getSystemContext() 方法都是被打上了 @UnsupportedAppUsage 注解的,且構造方法是使用 default 的限定符,外部無法直接實例化。而 ActivityThread 的類被打上了@hide標記,整個類對于外部來說都無法使用。
因此在構造 Context 的時候,需要通過 val activityThreadCls = Class.forName("android.app.ActivityThread") 來獲取到類對象,再改變構造方法的可訪問性來實例化 ActivityThread 對象

val activityThreadConstructor: Constructor<*> = activityThreadCls.getDeclaredConstructor()
activityThreadConstructor.isAccessible = true
val activityThread = activityThreadConstructor.newInstance()

再然后反射調用 getSystemContext() 方法

val getSystemContextMethod = activityThreadCls.getDeclaredMethod("getSystemContext")
getSystemContextMethod.invoke(activityThread) as Context

直接這樣構造出來之后,在使用上可能會因為缺少相關的初始化而不能使用,可以參考 scrcpy 的 Workarounds.java 將 ActivityThread.sCurrentActivityThreadactivityThread.mSystemThread 都賦值:

// ActivityThread.sCurrentActivityThread = activityThread;
val sCurrentActivityThreadField = activityThreadCls.getDeclaredField("sCurrentActivityThread")
sCurrentActivityThreadField.isAccessible = true
sCurrentActivityThreadField.set(null, activityThread)// activityThread.mSystemThread = true;
val mSystemThreadField = activityThreadCls.getDeclaredField("mSystemThread")
mSystemThreadField.isAccessible = true
mSystemThreadField.setBoolean(activityThread, true)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/906734.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/906734.shtml
英文地址,請注明出處:http://en.pswp.cn/news/906734.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【數據結構與算法】LeetCode 每日三題

如果你已經對數據結構與算法略知一二&#xff0c;現在正在復習數據結構與算法的一些重點知識 ------------------------------------------------------------------------------------------------------------------------- 點贊收藏&#x1f308;&#xff0c;每天更新總結文…

深度“求索”:DeepSeek+Dify構建個人知識庫

目錄 前言 環境部署 安裝Docker 安裝Dify 配置Dify 部署知識庫 創建應用 前言 在當今數字化信息爆炸的時代&#xff0c;數據隱私和個性化知識管理成為企業和個人關注的焦點。Dify&#xff0c;作為一款備受矚目的開源 AI 應用開發平臺&#xff0c;為用戶提供了完整的私有…

【Redis8】最新安裝版與手動運行版

目錄 一、直接運行 1. 下載 Redis百度網盤 2. 解壓后直接運行 redis-server.exe?編輯 二、安裝版運行 雙擊 install_redis_service.bat 輸入安裝路徑&#xff08;請提前創建好安裝路徑&#xff09;后直接回車?編輯 下一步直接回車即可&#xff0c;因為是使用配置模板…

@Column 注解屬性詳解

提示&#xff1a;文章旨在說明 Column 注解屬性如何在日常開發中使用&#xff0c;數據庫類型為 MySql&#xff0c;其他類型數據庫可能存在偏差&#xff0c;需要注意。 文章目錄 一、name 方法二、unique 方法三、nullable 方法四、insertable 方法五、updatable 方法六、column…

使用Gemini, LangChain, Gradio打造一個書籍推薦系統 (第二部分)

建立向量嵌入數據庫 from langchain_community.document_loaders import TextLoader from langchain_text_splitters import CharacterTextSplitter from langchain.docstore.document import Document from langchain_chroma.vectorstores import Chromaimport vertexai from…

【Go-4】函數

函數 函數是編程中的基本構建塊&#xff0c;用于封裝可重用的代碼邏輯。Go語言中的函數功能強大&#xff0c;支持多種特性&#xff0c;如多返回值、可變參數、匿名函數、閉包以及將函數作為值和類型傳遞。理解和掌握函數的使用對于編寫高效、可維護的Go程序至關重要。本章將詳…

【已解決】HBuilder X編輯器在外接顯示器或者4K顯示器怎么界面變的好小問題

觸發方式&#xff1a;主要涉及DPI縮放問題&#xff0c;可能在電腦息屏有概率觸發 修復方式&#xff1a; 1.先關掉軟件直接更改屏幕縮放&#xff0c;然后打開軟件&#xff0c;再關掉軟件恢復原來的縮放&#xff0c;再打開軟件就好了 2.(不推薦&#xff09;右鍵HBuilder在屬性里…

spark調度系統核心組件SparkContext、DAGSchedul、TaskScheduler、Taskset介紹

目錄 1. SparkContext2.DAGScheduler3. TaskScheduler4. 協作關系5 TaskSet的定義6. 組件關系說明Spark調度系統的核心組件主要有SparkContext、DAGScheduler和TaskScheduler SparkContext介紹 1. SparkContext 1、資源申請: SparkContext是Spark應用程序與集群管理器(如St…

VSCode+EIDE通過KeilC51編譯,使VSCode+EIDE“支持”C和ASM混編

在使用Keil C51時&#xff0c;要讓Keil C51支持混編則需要在混編的.c文件上右鍵選擇Options for File *(ALTF7)&#xff0c;打開選項界面后&#xff0c;在 Properties 頁 勾上 Generate Assembler SRC File 和 Assemble SRC File &#xff0c;如下圖所示&#xff1a; 這樣設置后…

SQLynx:一款跨平臺的企業級數據庫管理工具

SQLynx 是一款支持跨平臺&#xff08;Windows、Linux、macOS、Web&#xff09;的企業級數據庫管理和 SQL 工具&#xff0c;可以提供高效、安全且適配國產化技術棧的數據庫管理解決方案。 數據源 SQLynx 支持連接各種關系型數據庫、非關系型數據庫以及大數據平臺&#xff0c;包…

實戰項目8(實訓)

目錄 項目01 【sw1】配置 【sw2】配置 任務結果截圖 項目02 【sw1】配置 【sw2】配置 任務結果截圖 項目03 【sw1】配置 任務結果截圖 項目04 【sw1】配置 【r1】配置 任務結果截圖 項目05 【r1】配置 【r2】配置 【r3】配置 任務結果截圖 項目06 【r1】…

TCP為什么是三次握手,而不是二次?

為什么需要三次握手&#xff1f; 想象一下&#xff0c;你要給遠方的朋友寄一份重要文件。你會怎么做&#xff1f; 普通人的做法&#xff1a; 直接扔進郵箱&#xff0c;祈禱別丟了 聰明人的做法&#xff1a; 先打電話確認地址&#xff0c;再發快遞&#xff0c;最后確認收到 T…

dubbo使用nacos作為注冊中心配置

<dubbo:registry protocol"nacos" address"${dubbo.registry.address.nacos}" /> <dubbo:metadata-report address"${dubbo.metadata-report.address}"/> 如果有多個地址&#xff0c;這塊如何配置呢&#xff1f; nacos://ip:端口?…

教師角色的轉變:從知識傳授者到學習引導者

教師角色的轉變&#xff1a;從知識傳授者到學習引導者 隨著人工智能&#xff08;AI&#xff09;和信息技術的迅速發展&#xff0c;教育正在經歷深刻的變革。其中&#xff0c;教師角色的轉變尤為關鍵。傳統上&#xff0c;教師主要承擔“知識傳授者”的職責&#xff0c;即向學生…

PostgreSQL 用戶權限與安全管理

1 系統默認角色 postgres# select rolname from pg_roles; rolname ----------------------------- postgres pg_database_owner pg_read_all_data pg_write_all_data pg_monitor pg_read_all_settings pg_read_all_stats pg_stat_scan_tables …

C++構造函數和析構函數

C++構造函數和析構函數 C++的構造函數和析構函數是類的特殊成員函數,用于對象的創建和銷毀,分別在對象的生命周期開始和結束時自動調用。它們的使用對資源管理和對象的初始化/清理至關重要。 1. 構造函數 定義 構造函數在對象創建時自動調用,用于初始化對象的數據成員。構造…

根據Cortex-M3(STM32F1)權威指南講解MCU內存架構與如何查看編譯器生成的地址具體位置

首先我們先查看官方對于Cortex-M3預定義的存儲器映射 1.存儲器映射 1.1 Cortex-M3架構的存儲器結構 內部私有外設總線&#xff1a;即AHB總線&#xff0c;包括NVIC中斷&#xff0c;ITM硬件調試&#xff0c;FPB, DWT。 外部私有外設總線&#xff1a;即APB總線&#xff0c;用于…

軟件設計師“測試用例”考點分析——求三連

一、測試用例設計核心要點解析 1. 白盒測試覆蓋標準 &#xff08;1&#xff09;路徑覆蓋&#xff1a;需覆蓋程序中所有可能的路徑。如2018年真題路徑覆蓋需要3組測試用例&#xff08;①②、①③、①③④&#xff09;&#xff0c;2020年流程圖則需4個用例覆蓋ace/abd/abe/acd四…

Linux 用戶無法遠程連接服務器

前言 昨天深夜一點多接到客戶電話&#xff0c;客戶說OS用戶下午下班前還能正常登錄。因為晚上一點半需要關閉所有服務進行遷移&#xff0c;但是用戶無法登錄了&#xff0c;導致后續流程無法執行。我讓他先通過root用戶緊急修改了密碼&#xff0c;先保證業務正常流轉。 問題 …

多模態大語言模型arxiv論文略讀(八十八)

MammothModa: Multi-Modal Large Language Model ?? 論文標題&#xff1a;MammothModa: Multi-Modal Large Language Model ?? 論文作者&#xff1a;Qi She, Junwen Pan, Xin Wan, Rui Zhang, Dawei Lu, Kai Huang ?? 研究機構: ByteDance, Beijing, China ?? 問題背景…