该系列文章总纲链接:专题分纲目录 Android Framework 包管理子系统


本章关键点总结 & 说明:

导图是不断迭代的,这里主要关注➕ PkgMS安装应用部分。主要是三个步骤:从执行adb install 到复制文件,再到装载文件,最后到安装成功。

PackagerInstallerService特殊说明:PackagerInstallerService(PkgMIS)是android5.0中新加入的服务,一般通过PackagerInstallerService来分配一个SessinId,这个系统唯一的ID代表一次安装过程,如果一个应用的安装被中断了,比如设备重启,那么也可以通过这个ID来继续安装。Session的创建如下所示:

public int createSession(SessionParams params, String installerPackageName, int userId) {try {return createSessionInternal(params, installerPackageName, userId);} catch (IOException e) {throw ExceptionUtils.wrap(e);}
}

该方法会返回个系统唯一值作为SessionID,如果希望再次使用Session,可以通过接口openSession打开,代码实现如下:

public IPackageInstallerSession openSession(int sessionId) {try {return openSessionInternal(sessionId);} catch (IOException e) {throw ExceptionUtils.wrap(e);}
}

这里将返回一个IPackageInstallerSession(PackageInstallerSession的IBinder对象),在PackagerManagerService中用变量mSession来存所有的PackageInstallerSession对象。这些PackageInstallerSession对象保存了应用安装的相关数据,比如安装路径、安装进度等。。。

1 安装应用程序(从 adb install 到PkgMS)

一般我们会使用adb install 来安装应用,因此这里就从adb install开始分析,adb命令,install参数。直接看adb命令 处理install参数的代码,如下所示:

int adb_commandline(int argc, char **argv)
{char buf[4096];//...if (!strcmp(argv[0], "install")) {if (argc < 2) return usage();return install_app(ttype, serial, argc, argv);}//...if (!strcmp(argv[0], "uninstall")) {if (argc < 2) return usage();return uninstall_app(ttype, serial, argc, argv);}//...usage();return 1;
}

这里继续分析install_app实现,代码如下:

int install_app(transport_type transport, char* serial, int argc, char** argv)
{//这里需要设置复制目标的目录,如果安装在内部存储中,则目标目录为/data/local/tmp。static const char *const DATA_DEST = "/data/local/tmp/%s";//如果安装在SD卡上,则目标目录为/sdcard/tmp。static const char *const SD_DEST = "/sdcard/tmp/%s";//...for (i = 1; i < argc; i++) {if (!strcmp(argv[i], "-s")) {where = SD_DEST; //-s参数指明该APK安装到SD卡上}}//...char* apk_file = argv[last_apk];char apk_dest[PATH_MAX];snprintf(apk_dest, sizeof apk_dest, where, get_basename(apk_file));//将此APK传送到手机的目标路径int err = do_sync_push(apk_file, apk_dest, 0 /* no show progress */);if (err) {goto cleanup_apk;} else {argv[last_apk] = apk_dest; /* destination name, not source location */}//执行pm命令pm_command(transport, serial, argc, argv);cleanup_apk://手机中执行shell rm 命令,删除刚才传送过去的目标APK文件delete_file(transport, serial, apk_dest);return err;
}

这里关注pm_command方法,代码如下:

static int pm_command(transport_type transport, char* serial,int argc, char** argv)
{char buf[4096];snprintf(buf, sizeof(buf), "shell:pm");while(argc-- > 0) {char *quoted = escape_arg(*argv++);strncat(buf, " ", sizeof(buf) - 1);strncat(buf, quoted, sizeof(buf) - 1);free(quoted);}send_shellcommand(transport, serial, buf);return 0;
}

这里调用了send_shellcommand 发送"shell:pm install 参数"给手机端的adbd,这里继续分析pm命令,pm是脚本文件,构成如下:

# Script to start "pm" on the device,which has a very rudimentary
# shell.
#base=/system
export CLASSPATH=$base/frameworks/pm.jar
exec app_process $base/bincom.android.commands.pm.Pm "$@"

在编译system镜像时,Android.mk中会将该脚本复制到system/bin目录下。从pm脚本的内容来看,它就是通过app_process执行pm.jar包的main函数。pm的main函数实现如下:

public static void main(String[] args) {int exitCode = 1;exitCode = new Pm().run(args);//...System.exit(exitCode);
}

这里的run方法实现如下:

public int run(String[] args) throws IOException, RemoteException {//...//获取UserM 和 PkgMS的binder客户端mUm = IUserManager.Stub.asInterface(ServiceManager.getService("user"));mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));//...mInstaller = mPm.getPackageInstaller();//...//处理其他命令,这里仅考虑install的处理if ("install".equals(op)) {return runInstall();}//...
}

这里仅仅关注install部分相关代码,runInstall代码实现如下:

private int runInstall() {int installFlags = 0;//...while ((opt=nextOption()) != null) {if (opt.equals("-l")) {installFlags |= PackageManager.INSTALL_FORWARD_LOCK;} else if (opt.equals("-r")) {installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;//...各种参数解析} else {System.err.println("Error: Unknown option: " + opt);return 1;}}//...//获取Verification Package的文件位置final String verificationFilePath = nextArg();if (verificationFilePath != null) {System.err.println("\tver: " + verificationFilePath);verificationURI = Uri.fromFile(new File(verificationFilePath));} else {verificationURI = null;}//创建PackageInstallObserver,用于接收PkgMS的安装结果LocalPackageInstallObserver obs = new LocalPackageInstallObserver();try {VerificationParams verificationParams = new VerificationParams(verificationURI,originatingURI, referrerURI, VerificationParams.NO_UID, null);//这里调用PkgMS来安装应用程序mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,installerPackageName, verificationParams, abi, userId);synchronized (obs) {while (!obs.finished) {try {obs.wait();//等待安装结果} catch (InterruptedException e) {//...}}//...}} catch (RemoteException e) {//...}
}

这里调用了mPm.installPackageAsUser方法来处理“安装”,从 adb install 到PkgMS到此分析结束,接下来从这里开始便正式进入PkgMS来安装应用程序。

同时这里介绍另一种调用方式,给指定用户安装应用,安装应用程序也可以通过调用installPackage来实现,代码如下:

public void installPackage(String originPath, IPackageInstallObserver2 observer,int installFlags, String installerPackageName, VerificationParams verificationParams,String packageAbiOverride) {installPackageAsUser(originPath, observer, installFlags, installerPackageName, verificationParams,packageAbiOverride, UserHandle.getCallingUserId());}

这里实际上也是调用了installPackageAsUser方法。因此后面的安装应用程序步骤会从installPackageAsUser开始分析。

2 安装应用程序(复制文件)

2.1 流程分析

从installPackageAsUser开始分析 安装应用程序的步骤,代码如下所示:

public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,int installFlags, String installerPackageName, VerificationParams verificationParams,String packageAbiOverride, int userId) {//检查调用进程的权限mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);//检查调用进程的用户是否有安装权限final int callingUid = Binder.getCallingUid();enforceCrossUserPermission(callingUid, userId, true, true, "installPackageAsUser");//检查用户是否被限制 安装应用if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {try {if (observer != null) {//用户被限制,通知给监听者observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null);}} catch (RemoteException re) {}return;}if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {installFlags |= PackageManager.INSTALL_FROM_ADB;} else {installFlags &= ~PackageManager.INSTALL_FROM_ADB;installFlags &= ~PackageManager.INSTALL_ALL_USERS;}UserHandle user;//如果有INSTALL_ALL_USERS标志,则给所有用户安装if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {user = UserHandle.ALL;} else {user = new UserHandle(userId);}verificationParams.setInstallerUid(callingUid);final File originFile = new File(originPath);final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);//保存参数到InstallParams对象final Message msg = mHandler.obtainMessage(INIT_COPY);msg.obj = new InstallParams(origin, observer, installFlags,installerPackageName, verificationParams, user, packageAbiOverride);mHandler.sendMessage(msg);//发送消息
}

这里最后把调用的参数Installarams对象中,然后发送INIT_COPY消息。mHandler被绑定到另外一个工作线程(借助ThreadHandler对象的Looper)中,所以该INIT_COPY消息也将在那个工作线程中进行处理。接下来看handleMessage如何处理消息,代码如下:

public void handleMessage(Message msg) {try {doHandleMessage(msg);} finally {Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);}
}

继续分析doHandleMessage,这里只关注INIT_COPY相关处理,代码如下:

              case INIT_COPY: {HandlerParams params = (HandlerParams) msg.obj;int idx = mPendingInstalls.size();if (!mBound) {//绑定defaultContainerService服务if (!connectToService()) {//错误处理} else {//连接成功,把安装信息保存到mPendingInstallsmPendingInstalls.add(idx, params);}} else {//插入安装信息mPendingInstalls.add(idx, params);//如果安装请求队列之前的状态为空,则表明要启动安装if (idx == 0) {mHandler.sendEmptyMessage(MCS_BOUND);}}break;}

这里有一个前提,成功启动了DefaultContainerService(简称DCS)且idx为零,所以这是PkgMS首次处理安装请求,即将要处理MCS_BOUND消息。继续分析MCS_BOUND的处理,代码如下:

                case MCS_BOUND: {if (msg.obj != null) {mContainerService = (IMediaContainerService) msg.obj;}if (mContainerService == null) {//错误处理} else if (mPendingInstalls.size() > 0) {HandlerParams params = mPendingInstalls.get(0);if (params != null) {if (params.startCopy()) {//执行安装if (mPendingInstalls.size() > 0) {//工作完成,删除第一项mPendingInstalls.remove(0);}if (mPendingInstalls.size() == 0) {if (mBound) {//安装请求都处理完了,则需要和Service断绝联系,//通过发送MSC_UNB消息处理断交请求removeMessages(MCS_UNBIND);Message ubmsg = obtainMessage(MCS_UNBIND);sendMessageDelayed(ubmsg, 10000);}} else {//还有未处理的请求,则继续发送MCS_BOUNDmHandler.sendEmptyMessage(MCS_BOUND);}}}}break;}

这里先分析MCS_UNBIND的处理流程,代码如下所示:

                case MCS_UNBIND: {if (mPendingInstalls.size() == 0 && mPendingVerification.size() == 0) {if (mBound) {disconnectService();}} else if (mPendingInstalls.size() > 0) {mHandler.sendEmptyMessage(MCS_BOUND);}break;}

可以看到,如果没有请求则与服务断开联系,只要还有请求,就会继续发送MCS_BOUND消息。接下来继续分析startCopy,代码实现如下:

final boolean startCopy() {boolean res;try {if (++mRetries > MAX_RETRIES) {mHandler.sendEmptyMessage(MCS_GIVE_UP);handleServiceError();return false;} else {handleStartCopy();//调用派生类InstallParams的handleStartCopy函数res = true;}} catch (RemoteException e) {mHandler.sendEmptyMessage(MCS_RECONNECT);res = false;}//调用派生类InstallParams的handleReturnCode,返回处理结果handleReturnCode();return res;
}

这里继续分析关键方法InstallParams类的handleStartCopy,代码如下:

public void handleStartCopy() throws RemoteException {//...if (onInt && onSd) {// Check if both bits are setret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;} else {pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,packageAbiOverride);if (!origin.staged && pkgLite.recommendedInstallLocation== PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {//如果安装空间不够,释放cache空间final StorageManager storage = StorageManager.from(mContext);final long lowThreshold = storage.getStorageLowBytes(Environment.getDataDirectory());final long sizeBytes = mContainerService.calculateInstalledSize(origin.resolvedPath, isForwardLocked(), packageAbiOverride);if (mInstaller.freeCache(sizeBytes + lowThreshold) >= 0) {pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath,installFlags, packageAbiOverride);}if (pkgLite.recommendedInstallLocation== PackageHelper.RECOMMEND_FAILED_INVALID_URI) {pkgLite.recommendedInstallLocation= PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;}}}//...//创建InstallArgs对象,如果应用要求安装在SD卡或forward lock的应用//则创建AsecInstallArgs,否则创建FileInstallArgsfinal InstallArgs args = createInstallArgs(this);mArgs = args;if (ret == PackageManager.INSTALL_SUCCEEDED) {//...final int requiredUid = mRequiredVerifierPackage == null ? -1: getPackageUid(mRequiredVerifierPackage, userIdentifier);if (!origin.existing && requiredUid != -1&& isVerificationEnabled(userIdentifier, installFlags)) {//...校验相关} else {//无需校验则直接处理ret = args.copyApk(mContainerService, true);}}mRet = ret;
}

这里关注无需校验环节的copyApk(依赖于args的类,这里只从FileInstallArgs类开始分析)方法实现,代码如下:

int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {if (origin.staged) {codeFile = origin.file;resourceFile = origin.file;return PackageManager.INSTALL_SUCCEEDED;}try {//在data/app下生成临时文件final File tempDir = mInstallerService.allocateInternalStageDirLegacy();codeFile = tempDir;resourceFile = tempDir;} catch (IOException e) {return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;}final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() {//为临时文件创建文件描述符ParcelFileDescriptor,能够通过Binder传递@Overridepublic ParcelFileDescriptor open(String name, int mode) throws RemoteException {if (!FileUtils.isValidExtFilename(name)) {throw new IllegalArgumentException("Invalid filename: " + name);}try {final File file = new File(codeFile, name);final FileDescriptor fd = Os.open(file.getAbsolutePath(),O_RDWR | O_CREAT, 0644);Os.chmod(file.getAbsolutePath(), 0644);return new ParcelFileDescriptor(fd);} catch (ErrnoException e) {throw new RemoteException("Failed to open: " + e.getMessage());}}};int ret = PackageManager.INSTALL_SUCCEEDED;//复制文件ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);if (ret != PackageManager.INSTALL_SUCCEEDED) {return ret;}//安装系统自带的native动态库final File libraryRoot = new File(codeFile, LIB_DIR_NAME);NativeLibraryHelper.Handle handle = null;try {handle = NativeLibraryHelper.Handle.create(codeFile);ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,abiOverride);} catch (IOException e) {ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;} finally {IoUtils.closeQuietly(handle);}return ret;
}

至此,安装的第一部份工作完成,应用安装到/data/app目录下。

2.2 关键类说明

HandlerParams和InstallArgs介绍,关系如下所示:

HandlerParams和InstallArgs均为抽象类。HandlerParams有三个子类:InstallParams、MoveParams和MeasureParams:

  1. InstallParams 处理APK的安装。
  2. MoveParams 处理某个已安装APK的搬家请求(例如从内部存储移动到SD卡上)。
  3. MeasureParams 查询某个已安装的APK占据存储空间的大小(例如在设置程序中得到的某个APK使用的缓存文件的大小)。

对于InstallParams来说,它还有两个伴儿,即InstallArgs的派生类FileInstallArgs和AsecInstallArgs。FileInstallArgs针对的是安装在内部存储的APK,而AsecInstallArgs针对的是那些安装在SD卡上的APK。(整个流程的分析,包括后面,都是针对FileInstallArgs来做,AsecInstallArgs分析方式类似)

3 应用安装第二阶段:装载应用

本阶段是将应用格式转换成oat格式,为应用创建数据目录,最后把应用的信息装载到PkgMS的数据结构中,在前面startCopy函数中最后调用了handleReturnCode方法,代码如下:

void handleReturnCode() {if (mArgs != null) {processPendingInstall(mArgs, mRet);}
}

继续分析processPendingInstall,代码实现如下:

private void processPendingInstall(final InstallArgs args, final int currentStatus) {// Queue up an async operation since the package installation may take a little while.mHandler.post(new Runnable() {public void run() {mHandler.removeCallbacks(this);//防止重复调用PackageInstalledInfo res = new PackageInstalledInfo();res.returnCode = currentStatus;res.uid = -1;res.pkg = null;res.removedInfo = new PackageRemovedInfo();//如果应用安装成功,则装载安装的应用if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {//InstallArgs的doPreInstall函数args.doPreInstall(res.returnCode);synchronized (mInstallLock) {//关键函数,进行APK安装installPackageLI(args, res);}//FileInstallArgs的doPostInstall函数args.doPostInstall(res.returnCode, res.uid);}//执行备份相关代码(通过BackupManagerService来备份处理)...if (!doRestore) {//发送POST_INSTALL消息Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);mHandler.sendMessage(msg);}}});
}

该方法总结如下:

  1. 这里的InstallArgs的doPreInstall函数在本例中是FileInstallArgs的doPreInstall函数。
  2. 调用PkgMS的installPackageLI函数进行APK安装,接下来将深度分析该方法。
  3. 这里的InstallArgs的doPostInstall函数在本例中是FileInstallArgs的doPostInstall函数。
  4. 此时,该APK已经安装完成(不论失败还是成功),继续向mHandler抛送一个POST_INSTALL消息,该消息携带一个token,通过它可从mRunningInstalls数组中取得一个PostInstallData对象。后面也将继续分析handler对POST_INSTALL的处理流程。

接下来将针对{步骤2}的installPackageLI和{步骤4}的handler消息处理进行详细分析。

@1 installPackageLI实现

private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {//...final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)| (onSd ? PackageParser.PARSE_ON_SDCARD : 0);//创建apk文件的解析器PackageParser pp = new PackageParser();pp.setSeparateProcesses(mSeparateProcesses);pp.setDisplayMetrics(mMetrics);final PackageParser.Package pkg;try {//解析apk文件pkg = pp.parsePackage(tmpPackageFile, parseFlags);} catch (PackageParserException e) {res.setError("Failed parse during installPackageLI", e);return;}//收集应用的签名try {pp.collectCertificates(pkg, parseFlags);pp.collectManifestDigest(pkg);} catch (PackageParserException e) {res.setError("Failed collect during installPackageLI", e);return;}//...pp = null;String oldCodePath = null;boolean systemApp = false;synchronized (mPackages) {//升级判定及相关处理int N = pkg.permissions.size();//permission相关处理}//有系统标志的应用要求装在sd卡上,报错退出if (systemApp && onSd) {res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,"Cannot install updates to system apps on sdcard");return;}//如果更名失败,则退出if (!args.doRename(res.returnCode, pkg, oldCodePath)) {res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");return;}if (replace) {//如果安装的是升级包replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,installerPackageName, res);} else {//如果是新应用installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,args.user, installerPackageName, res);}synchronized (mPackages) {final PackageSetting ps = mSettings.mPackages.get(pkgName);if (ps != null) {res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);}}
}

installPackageLI首先解析了安装的应用文件,得到解析的结果后,主要是判断新安装应用是否和应用系统中已安装应用构成升级关系,是则调用replacePackageLI来处理,否则就用installNewPackageLI来处理。

@2 POST_INSTALL消息处理

相关代码如下:

case POST_INSTALL: {PostInstallData data = mRunningInstalls.get(msg.arg1);mRunningInstalls.delete(msg.arg1);boolean deleteOld = false;if (data != null) {InstallArgs args = data.args;PackageInstalledInfo res = data.res;if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {res.removedInfo.sendBroadcast(false, true, false);Bundle extras = new Bundle(1);extras.putInt(Intent.EXTRA_UID, res.uid);//...//发送广播ACTION_PACKAGE_ADDEDsendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,res.pkg.applicationInfo.packageName,extras, null, null, firstUsers);final boolean update = res.removedInfo.removedPackage != null;if (update) {extras.putBoolean(Intent.EXTRA_REPLACING, true);}sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,res.pkg.applicationInfo.packageName,extras, null, null, updateUsers);if (update) {//如果是升级包,则发送更多的广播sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,res.pkg.applicationInfo.packageName,extras, null, null, updateUsers);sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,null, null,res.pkg.applicationInfo.packageName, null, updateUsers);//如果是forward lock的应用,或者安装在sd卡上的应用,发送广播if (isForwardLocked(res.pkg) || isExternal(res.pkg)) {int[] uidArray = new int[] { res.pkg.applicationInfo.uid };ArrayList<String> pkgList = new ArrayList<String>(1);pkgList.add(res.pkg.applicationInfo.packageName);sendResourcesChangedBroadcast(true, true,pkgList,uidArray, null);}}if (res.removedInfo.args != null) {deleteOld = true;}}//...if (args.observer != null) {try {//通知调用者 安装的结果Bundle extras = extrasForInstallResult(res);args.observer.onPackageInstalled(res.name, res.returnCode,res.returnMsg, extras);} catch (RemoteException e) {Slog.i(TAG, "Observer no longer exists.");}}}
} break;

整个消息处理主要是发送广播,应用安装成功后通知其他的应用开始处理,比如Launcher中需要添加的图标等。。。最后通过回调函数通知 安装者 安装的结果。

Android Framework 包管理子系统(03)应用安装相关推荐

  1. 在android中如何调用系统的程序安装器来安装apk

    Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFi ...

  2. 专题总纲目录 Android Framework 总纲

    专题总纲说明: 本系列文章虽说是 Android 的知识体系专题,同时也是学习Android Framework 系统的一个思路,尤其是当我们对Android 框架层 一点都不了解的时候,但前提是要有 ...

  3. Android 系统(78)---《android framework常用api源码分析》之 app应用安装流程

    <android framework常用api源码分析>之 app应用安装流程 <android framework常用api源码分析>android生态在中国已经发展非常庞大 ...

  4. Android FrameWork学习(二)Android系统源码调试

    点击打开链接 通过上一篇 Android FrameWork学习(一)Android 7.0系统源码下载\编译 我们了解了如何进行系统源码的下载和编译工作. 为了更进一步地学习跟研究 Android ...

  5. Android FrameWork 系统源码调试

    这是很久以前访问掘金的时候 无意间看到的一个关于Android的文章,作者更细心,分阶段的将学习步骤记录在自己博客中,我觉得很有用,想作为分享同时也是留下自己知识的一些欠缺收藏起来,今后做项目的时候会 ...

  6. Android FrameWork 学习之Android 系统源码调试

    这是很久以前访问掘金的时候 无意间看到的一个关于Android的文章,作者更细心,分阶段的将学习步骤记录在自己博客中,我觉得很有用,想作为分享同时也是留下自己知识的一些欠缺收藏起来,今后做项目的时候会 ...

  7. 【Android】安装Android Studio、破解Source Insight+查看Android Framework源码

    文章目录 安装Android Studio 查看Framework源码 1.Source Insight安装 2.Framework源码下载 3.加载源码 安装Android Studio 官网地址: ...

  8. android/WINCE驱动、系统(framework)和apk/app产品开发和合作

    本人有丰富的android/WINCE驱动和系统开发经验,和精通android/WINCE系统与apk/app的同事组合的一个开发团队,团队特点: 1.精通android/WINCE驱动.系统(fra ...

  9. Android FrameWork学习(一)Android 7 0系统源码下载 编译

    最近计划着研究下 Android 7.0 的系统源码,之前也没做过什么记录,这次正好将学习的内容记录下来,方便以后复习巩固. 既然要学习我们的系统源码,那我们第一步要做的就是下载源码并进行编译了. # ...

最新文章

  1. java 连接池 druid_从零开始学 Java - 数据库连接池的选择 Druid
  2. 谁是谢源?广西理科探花、清华电子系学霸、阿里AI芯片带头大哥、新晋ACM Fellow...
  3. 变量命名规范 匈牙利 下划线 骆驼 帕斯卡
  4. c# 串口最简单接收十六进制
  5. [转]Open Data Protocol (OData) Basic Tutorial
  6. MySQL Administrator
  7. 《大型网站服务器容量规划》一1.1 容量规划背景
  8. 2014_anshan_onsite
  9. [HAOI2010]软件安装(树形背包,tarjan缩点)
  10. 大数据---(3)金融数据架构
  11. 使用VMware创建一个虚拟机,并安装乌班图系统
  12. 区块链开发入门教程【加精】
  13. OSI七层协议模型与记忆口诀
  14. java 10000以内的质数_10000以内的质数表.doc
  15. tmap | 制作地图动画、放大局部区域
  16. 利用python创建自定义的股票指数
  17. 【XSY2515】管道(pipe)(最小生成树+倍增lca)
  18. linux-011之termios.h头文件
  19. 实现游戏中的轮廓描边
  20. mysql中的方差函数_请问关于SQL中的方差函数VAR()相关问题?

热门文章

  1. 【python】都2022年不会还有人不会在电脑桌面上养宠物吧~
  2. iOS系统神奇app,别以为你穿了马甲我就找不到你了
  3. 基层社会治理综合管理平台智慧街道Java商业源码
  4. Why Memory Barriers?中文翻译(上)
  5. 诺基亚e65 ucweb 6.7正式免签名下载
  6. 让职场人早下班的PDF转Word技巧,速戳!
  7. Web基础(三)Python Web
  8. 投稿时Cover Letter的重要性(部分体会来自导师)
  9. 将数字划分为素数的乘积
  10. Chrome浏览器运行超图三维场景