Android app 설치 프로세스 분석(Nougat 기반)
전원 켜기 설치
scanDirTracedLI // ,
scanDirLI
이 두 가지 방법을 사용하여 scanDirLI부터 추적하여 설치하는 것입니다.
private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
final File[] files = dir.listFiles();
...
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
// Ignore entries which are not packages
continue;
}
try {
scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
// Delete invalid userdata apps
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
removeCodePathLI(file);
}
}
}
}
계속해서 scanPackageTracedLI 메서드를 살펴보십시오.
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
pp.setDisplayMetrics(mMetrics);
if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
}
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);
}
우리는 이 방법에서부터 Parse Package를 볼 수 있다. Package Parser를 통해 apk 파일을 해석하면 Package Parser의 파일에 가서 이 Parse Package 방법을 볼 수 있다. 바로 설치 패키지의 설정 파일을 해석하고 Package에 저장하여 되돌려주는 것이다. 중간에 엎치락뒤치락하며 설정 파일의 여러 가지 상황에 대해 Parse를 한다.가장 중요한 것은parseBaseApkCommon 방법이다. 이 방법은 manifast 파일의 라벨을 분석하고 그 중에서parseBaseApplication 방법을 호출하여 응용 프로그램 라벨을 분석한다.
그리고 scanPackageLI->scanPackageDirtyLI, 후자의 방법에서 우리는 이전에 해석한
PackageParser.Provider p = pkg.providers.get(i);
p.info.processName = fixProcessName(pkg.applicationInfo.processName,
p.info.processName, pkg.applicationInfo.uid);
mProviders.addProvider(p);
PackageParser.Service s = pkg.services.get(i);
s.info.processName = fixProcessName(pkg.applicationInfo.processName,
s.info.processName, pkg.applicationInfo.uid);
mServices.addService(s);
등등, 우리는 해당하는provider, 서비스,receiver,activity를 모두 PMS의 구성원 집합 클래스에 저장할 것이다.
이 시스템에 적용된 설치는 모두 완성된 셈이다. 아마도 당신은 우리가 어떤 인스타그램의 어떤 방법을 보았는지 말할 수 있을 것이다. 사실 가장 정수적인 것은 위에 있는 마지막 1000+줄 덩어리의 방법이다. 이 안에 있는 모든 Parser가 나온 정보를 PMS에 저장한 것이다. 이것이 바로 설치라는 것이다.설치는 apk의 정보를 분석하여 PMS에 저장하는 과정입니다. 그리고launcher에 아이콘을 생성하여 사용자가 열 수 있도록 합니다. 단지 이것뿐입니다.
네트워크에서 어플리케이션 설치 다운로드
최종적으로 다음과 같은 방식으로 설치를 진행하였다
String fileName = "/mnt/usb/sda4/test.apk";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(new File(fileName)), "application/vnd.android.package-archive");
startActivity(intent);
Package Installer Activity를 시작합니다. 이 activity는 안드로이드의 시스템 응용 프로그램 중 하나입니다. 원본 코드는 패키지/app/package installer에서 구체적인 원본 코드는 아래 링크로 볼 수 있습니다.https://github.com/android/platform_packages_apps_packageinstaller/blob/master/src/com/android/packageinstaller/PackageInstallerActivity.java우리는 그 onCreate 방법에서 본 후에 checkIfAllowedAndInitiateInstall->initiateInstall->startInstallConfirm을 호출하여 권한에 관한 문제를 처리할 것입니다
다음에 아래 ok 단추를 누르면 설치 절차에 들어갑니다.
public void onClick(View v) {
if (v == mOk) {
if (mOkCanInstall || mScrollView == null) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
clearCachedApkIfNeededAndFinish();
} else {
startInstall();
}
} else {
mScrollView.pageScroll(View.FOCUS_DOWN);
}
} else if (v == mCancel) {
...
}
다음으로는 Start Install 방법을 살펴보겠습니다.
private void startInstall() {
// Start subactivity to actually install the application
Intent newIntent = new Intent();
...
newIntent.setClass(this, InstallAppProgress.class);
...
startActivity(newIntent);
finish();
}
Install App Progress라는 activity로 넘어가면 우리가 설치한 후에 들어간 설치 인터페이스가 뚜렷하다. 진행 표시줄이 계속 깜빡이는 설치 중인 인터페이스가 있다.onCreate()->initView():
void initView() {
setContentView(R.layout.op_progress);
...
if ("package".equals(mPackageURI.getScheme())) {
try {
pm.installExistingPackage(mAppInfo.packageName);
onPackageInstalled(PackageInstaller.STATUS_SUCCESS);
} catch (PackageManager.NameNotFoundException e) {
onPackageInstalled(PackageInstaller.STATUS_FAILURE_INVALID);
}
} else {
...
mInstallHandler.post(new Runnable() {
@Override
public void run() {
doPackageStage(pm, params);
}
});
}
}
좀 간소화하였는데, 주로 이 두 가지 방법이다.
installExistingPackage
하나는pm.install Existing Package, 실제로 호출된 것이 Application Package Manager의 install Existing Package라는 것을 알고 있습니다
@Override
public int installExistingPackage(String packageName) throws NameNotFoundException {
return installExistingPackageAsUser(packageName, mContext.getUserId());
}
@Override
public int installExistingPackageAsUser(String packageName, int userId)
throws NameNotFoundException {
try {
int res = mPM.installExistingPackageAsUser(packageName, userId);
if (res == INSTALL_FAILED_INVALID_URI) {
throw new NameNotFoundException("Package " + packageName + " doesn't exist");
}
return res;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
계속해서 우리는 PMS의 install Existing Package Asuser 방법을 찾아갔는데 구체적인 방법은 붙이지 않았다. 실제적인 의미는 이 설치 패키지가 이미 설치되었다는 것이다. 단지 현재 사용자가 설치하지 않았기 때문에 백엔드의 설정을 처리할 것이다. 실제적으로 설치한 물건과 관련이 없다.다른 방법을 보도록 하겠습니다.
doPackageStage
private void doPackageStage(PackageManager pm, PackageInstaller.SessionParams params) {
final PackageInstaller packageInstaller = pm.getPackageInstaller();
PackageInstaller.Session session = null;
try {
session = packageInstaller.openSession(sessionId);
...
session.commit(pendingIntent.getIntentSender());
} catch (IOException e) {
onPackageInstalled(PackageInstaller.STATUS_FAILURE);
} finally {
IoUtils.closeQuietly(session);
}
}
그중에서session에 대해 많은 조작을 했습니다. 우리는 가장 중요한 마지막session만 보았습니다.commit,
OK, 이어서 Package Installer로 들어가겠습니다.javasession의commit방법
public void commit(@NonNull IntentSender statusReceiver) {
try {
mSession.commit(statusReceiver);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
여기 mSession 저희가 봤을 때는...
private IPackageInstallerSession mSession;
/** {@hide} */
public Session(IPackageInstallerSession session) {
mSession = session;
}
극악무도한 binder,session의 정의로 돌아가기
session = packageInstaller.openSession(sessionId);
오픈 세션 방법 찾기
public @NonNull Session openSession(int sessionId) throws IOException {
try {
return new Session(mInstaller.openSession(sessionId));
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
mInstaller란 무엇인지,
private final IPackageInstaller mInstaller;
또 하나의 binder가 오면 우리는 한 걸음 한 걸음 붙이지 않을 것이다. 독자도 스스로 최종 실현을 찾을 수 있다고 믿는 것은 Package Installer Session이다.
@Override
public void commit(IntentSender statusReceiver) {
...
final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}
부분을 생략하고 마지막에 Handler한테 메시지를 보냈어요.
mHandler = new Handler(looper, mHandlerCallback);
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
// Cache package manager data without the lock held
final PackageInfo pkgInfo = mPm.getPackageInfo(
params.appPackageName, PackageManager.GET_SIGNATURES /*flags*/, userId);
final ApplicationInfo appInfo = mPm.getApplicationInfo(
params.appPackageName, 0, userId);
synchronized (mLock) {
if (msg.obj != null) {
mRemoteObserver = (IPackageInstallObserver2) msg.obj;
}
try {
commitLocked(pkgInfo, appInfo);
} catch (PackageManagerException e) {
final String completeMsg = ExceptionUtils.getCompleteMessage(e);
Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
destroyInternal();
dispatchSessionFinished(e.error, completeMsg, null);
}
return true;
}
}
};
중요한 것은 바로 그commitLocked이다. 이 방법은 비교적 길고 여전히 권한과 설정 방면의 일을 하는데 방법의 마지막에 볼 수 있다.
mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
installerPackageName, installerUid, user, mCertificates);
PMS의 InstallStage 메서드로 이동합니다.
void installStage(String packageName, File stagedDir, String stagedCid,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
String installerPackageName, int installerUid, UserHandle user,
Certificate[][] certificates) {
...
final Message msg = mHandler.obtainMessage(INIT_COPY);
final InstallParams params = new InstallParams(origin, null, observer,
sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
verificationInfo, user, sessionParams.abiOverride,
sessionParams.grantedRuntimePermissions, certificates);
...
msg.obj = params;
...
mHandler.sendMessage(msg);
}
우리는 전체 과정을 상기 세 개의 코드로 간소화하여 what를 INIT 로 보낸다COPY 메시지
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
// , false, mBound true,
// else
if (!mBound) {
Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
System.identityHashCode(mHandler));
// If this is the only one pending we might
// have to bind to the service again.
if (!connectToService()) {
Slog.e(TAG, "Failed to bind to media container service");
params.serviceError();
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
System.identityHashCode(mHandler));
if (params.traceMethod != null) {
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, params.traceMethod,
params.traceCookie);
}
return;
} else {
// Once we bind to the service, the first
// pending request will be processed.
mPendingInstalls.add(idx, params);
}
} else {
mPendingInstalls.add(idx, params);
// Already bound to the service. Just make
// sure we trigger off processing the first request.
if (idx == 0) {
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
break;
}
바로 마지막에 MCS를 보내주셨는데...BOUND 메시지
case MCS_BOUND: {
...
// , else if
if (mContainerService == null) {
...
}
// 0
else if (mPendingInstalls.size() > 0) {
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
System.identityHashCode(params));
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");
// copy
if (params.startCopy()) {
// We are done... look for more work or to
// go idle.
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Checking for more work or unbind...");
// Delete pending install
if (mPendingInstalls.size() > 0) {
mPendingInstalls.remove(0);
}
// ,
// MCS_BOUND ,
if (mPendingInstalls.size() == 0) {
if (mBound) {
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Posting delayed MCS_UNBIND");
removeMessages(MCS_UNBIND);
Message ubmsg = obtainMessage(MCS_UNBIND);
// Unbind after a little delay, to avoid
// continual thrashing.
sendMessageDelayed(ubmsg, 10000);
}
} else {
// There are more pending requests in queue.
// Just post MCS_BOUND message to trigger processing
// of next pending install.
if (DEBUG_SD_INSTALL) Log.i(TAG,
"Posting MCS_BOUND for next work");
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
} else {
// Should never happen ideally.
Slog.w(TAG, "Empty queue");
}
break;
}
바로 startCopy로 이동합니다.
final boolean startCopy() {
boolean res;
try {
if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
// ,
if (++mRetries > MAX_RETRIES) {
Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
mHandler.sendEmptyMessage(MCS_GIVE_UP);
handleServiceError();
return false;
} else {
//
handleStartCopy();
res = true;
}
} catch (RemoteException e) {
if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
mHandler.sendEmptyMessage(MCS_RECONNECT);
res = false;
}
// return code
handleReturnCode();
return res;
}
먼저handleStartCopy 방법을 살펴보겠습니다. 발견시 추상적인 방법, 구체적인 실현을 보면 인터넷에서 InstallParams를 찾을 수 있습니다.
public void handleStartCopy() throws RemoteException {
int ret = PackageManager.INSTALL_SUCCEEDED;
// sdcard ,
if (origin.staged) {
if (origin.file != null) {
installFlags |= PackageManager.INSTALL_INTERNAL;
installFlags &= ~PackageManager.INSTALL_EXTERNAL;
} else if (origin.cid != null) {
installFlags |= PackageManager.INSTALL_EXTERNAL;
installFlags &= ~PackageManager.INSTALL_INTERNAL;
} else {
throw new IllegalStateException("Invalid stage location");
}
}
...
// APK
if (onInt && onSd) {
// Check if both bits are set.
Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else if (onSd && ephemeral) {
Slog.w(TAG, "Conflicting flags specified for installing ephemeral on external");
ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
} else {
...
}
...
// createInstallArgs
final InstallArgs args = createInstallArgs(this);
if (ret == PackageManager.INSTALL_SUCCEEDED) {
...
// InstallArgs copyApk
ret = args.copyApk(mContainerService, true);
}
}
mRet = ret;
}
주요 업무는 apk 메시지를 지정한 위치로 복사하고handle ReturnCode를 보는 것이다
void handleReturnCode() {
// If mArgs is null, then MCS couldn't be reached. When it
// reconnects, it will try again to install. At that point, this
// will succeed.
if (mArgs != null) {
processPendingInstall(mArgs, mRet);
}
}
프로세스 Pending Install () ->install Package Traced LI () ->install Package LI () 는 전체 방법이 비교적 길고 주로 목록 파일을 분석하고 인증서를 얻으며 서명을 추출하는 작업을 했습니다. 이 방법에서 다음과 같은 코드 세그먼트를 찾을 수 있습니다. 바로 이 코드가 설치를 처리했습니다.
try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
"installPackageLI")) {
if (replace) {
// 4. packages
replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
installerPackageName, res);
} else {
// 5. packages
installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
args.user, installerPackageName, volumeUuid, res);
}
}
우리 계속
/*
* Install a non-existing package.
*/
private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
int scanFlags, UserHandle user, String installerPackageName, String volumeUuid,
PackageInstalledInfo res) {
...
try {
PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags,
System.currentTimeMillis(), user);
updateSettingsLI(newPackage, installerPackageName, null, res, user);
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
prepareAppDataAfterInstallLIF(newPackage);
} else {
// Remove package from internal structures, but keep around any
// data that might have already existed
deletePackageLIF(pkgName, UserHandle.ALL, false, null,
PackageManager.DELETE_KEEP_DATA, res.removedInfo, true, null);
}
} catch (PackageManagerException e) {
res.setError("Package couldn't be installed in " + pkg.codePath, e);
}
...
}
삭제와 생략을 통해 우리는 우리가 익숙한 방법인 scanPackageTracedLI를 보았고 구체적인 절차는 상기 시스템 응용 설치를 보았다.
마지막으로 또 다른 ADB의 설치를 보도록 하겠습니다.
adb 설치
명령줄 창에adb install을 입력할 때 실제로 시스템은 다음과 같은 프로그램을 실행합니다
db_commandline
install_app_legacy or install_app
pm_command
send_shell_command
Pm.runInstall()
이 과정은 apk 파일copy를 데이터/local/tmp/디렉터리에 보내고 셸 서비스에pm 명령을 보내서 apk를 설치하고 마지막으로 Pm를 호출합니다.runInstall () 방법으로 apk를 설치합니다.우리는 이 방법을 볼 수 있다.
private int runInstall() throws RemoteException {
...
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
try {
if (inPath == null && params.sessionParams.sizeBytes == 0) {
System.err.println("Error: must either specify a package size or an APK file");
return 1;
}
if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
return 1;
}
if (doCommitSession(sessionId, false /*logSuccess*/)
!= PackageInstaller.STATUS_SUCCESS) {
return 1;
}
System.out.println("Success");
return 0;
} finally {
try {
mInstaller.abandonSession(sessionId);
} catch (Exception ignore) {
}
}
}
우리는 여전히 생략하고 보며 위의 이 단락을 생략하고 맨 아래에서 세 가지 중요한 방법을 직접 보았다. 바로doCreate Session,doWrite Session,doCommit Session, 마지막 것을 클릭한다.
private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException {
PackageInstaller.Session session = null;
try {
session = new PackageInstaller.Session(
mInstaller.openSession(sessionId));
final LocalIntentReceiver receiver = new LocalIntentReceiver();
session.commit(receiver.getIntentSender());
final Intent result = receiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
if (logSuccess) {
System.out.println("Success");
}
} else {
System.err.println("Failure ["
+ result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
}
return status;
} finally {
IoUtils.closeQuietly(session);
}
}
익숙한 세션을 봤어요.commit인가,
총결산
다음은 인터페이스를 설치하는 app 설치 절차가 있고adb 설치를 중간에 삽입하고 인터페이스를 설치하지 않은 시스템 설치입니다.
adb install 1.Pm.runInstall() 2.Pm.doCommitSession()
1.scanPackageTracedLI 2.scanPackageLI 3.scanPackageDirtyLI
이상!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.