应用程序包的安装是android的特点

APK为AndroidPackage的缩写

Android应用安装有如下四种方式:

1.系统应用安装――开机时完成,没有安装界面

2.网络下载应用安装――通过market应用完成,没有安装界面

3.ADB工具安装――没有安装界面。

4.第三方应用安装――通过SD卡里的APK文件安装,有安装界面,由         packageinstaller.apk应用处理安装及卸载过程的界面。

应用安装的流程及路径 
应用安装涉及到如下几个目录:

system/app ---------------系统自带的应用程序,获得adb root权限才能删除

data/app  ---------------用户程序安装的目录。安装时把                                                                                                      apk文件复制到此目录
data/data ---------------存放应用程序的数据
data/dalvik-cache--------将apk中的dex文件安装到dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一)

安装过程:

复制APK安装包到data/app目录下,解压并扫描安装包,把dex文件(Dalvik字节码)保存到dalvik-cache目录,并data/data目录下创建对应的应用数据目录。

卸载过程:

删除安装过程中在上述三个目录下创建的文件及目录。

安装应用的过程解析

一.开机安装 
PackageManagerService处理各种应用的安装,卸载,管理等工作,开机时由systemServer启动此服务

(源文件路径:android\frameworks\base\services\java\com\android\server\PackageManagerService.java)

PackageManagerService服务启动的流程:

1.首先扫描安装“system\framework”目录下的jar包

[java] view plaincopy
  1. // Find base frameworks (resource packages without code).
  2. mFrameworkInstallObserver = new AppDirObserver(
  3. mFrameworkDir.getPath(), OBSERVER_EVENTS, true);
  4. mFrameworkInstallObserver.startWatching();
  5. scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM
  6. | PackageParser.PARSE_IS_SYSTEM_DIR,
  7. scanMode | SCAN_NO_DEX, 0);

2.扫描安装系统system/app的应用程序

[java] view plaincopy
  1. // Collect all system packages.
  2. mSystemAppDir = new File(Environment.getRootDirectory(), "app");
  3. mSystemInstallObserver = new AppDirObserver(
  4. mSystemAppDir.getPath(), OBSERVER_EVENTS, true);
  5. mSystemInstallObserver.startWatching();
  6. scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM
  7. | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);

3.制造商的目录下/vendor/app应用包

[java] view plaincopy
  1. // Collect all vendor packages.
  2. mVendorAppDir = new File("/vendor/app");
  3. mVendorInstallObserver = new AppDirObserver(
  4. mVendorAppDir.getPath(), OBSERVER_EVENTS, true);
  5. mVendorInstallObserver.startWatching();
  6. scanDirLI(mVendorAppDir, PackageParser.PARSE_IS_SYSTEM
  7. | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);

4.扫描“data\app”目录,即用户安装的第三方应用

[java] view plaincopy
  1. scanDirLI(mAppInstallDir, 0, scanMode, 0);

5.扫描" data\app-private"目录,即安装DRM保护的APK文件(一个受保护的歌曲或受保 护的视频是使用 DRM 保护的文件)

[java] view plaincopy
  1. scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
  2. scanMode, 0);

扫描方法的代码清单

[java] view plaincopy
  1. private void scanDirLI(File dir, int flags, int scanMode, long currentTime) {
  2. String[] files = dir.list();
  3. if (files == null) {
  4. Log.d(TAG, "No files in app dir " + dir);
  5. return;
  6. }
  7. if (false) {
  8. Log.d(TAG, "Scanning app dir " + dir);
  9. }
  10. int i;
  11. for (i=0; i<files.length; i++) {
  12. File file = new File(dir, files[i]);
  13. if (!isPackageFilename(files[i])) {
  14. // Ignore entries which are not apk's
  15. continue;
  16. }
  17. PackageParser.Package pkg = scanPackageLI(file,
  18. flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime);
  19. // Don't mess around with apps in system partition.
  20. if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
  21. mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {
  22. // Delete the apk
  23. Slog.w(TAG, "Cleaning up failed install of " + file);
  24. file.delete();
  25. }
  26. }
  27. }

并且从该扫描方法中可以看出调用了scanPackageLI()

private PackageParser.Package scanPackageLI(File scanFile,

int parseFlags, int scanMode, long currentTime)

跟踪scanPackageLI()方法后发现,程序经过很多次的if else 的筛选,最后判定可以安装后调用了 mInstaller.install

[java] view plaincopy
  1. if (mInstaller != null) {
  2. int ret = mInstaller.install(pkgName, useEncryptedFSDir,  pkg.applicationInfo.uid,pkg.applicationInfo.uid);
  3. if(ret < 0) {
  4. // Error from installer
  5. mLastScanError =    PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
  6. return null;
  7. }
  8. }

mInstaller.install()  通过

LocalSocketAddress address = new LocalSocketAddress(

"installd", LocalSocketAddress.Namespace.RESERVED);

指挥installd在C语言的文件中完成工作

PackageManagerService小节 :1)从apk, xml中载入pacakge信息, 存储到内部成员变量中, 用于后面的查找. 关键的方法是scanPackageLI().
2)各种查询操作, 包括query Intent操作.
3)install package和delete package的操作. 还有后面的关键方法是installPackageLI().

二、从网络上下载应用:

下载完成后,会自动调用Packagemanager的安装方法installPackage()

/* Called when a downloaded package installation has been confirmed by the user */

由英文注释可见PackageManagerService类的installPackage()函数为安装程序入口。

[java] view plaincopy
  1. public void installPackage(
  2. final Uri packageURI, final IPackageInstallObserver observer, final int flags,
  3. final String installerPackageName) {
  4. mContext.enforceCallingOrSelfPermission(
  5. android.Manifest.permission.INSTALL_PACKAGES, null);
  6. Message msg = mHandler.obtainMessage(INIT_COPY);
  7. msg.obj = new InstallParams(packageURI, observer, flags,
  8. installerPackageName);
  9. mHandler.sendMessage(msg);
  10. }

其中是通过PackageHandler的实例mhandler.sendMessage(msg)把信息发给继承Handler的类HandleMessage()方法

[java] view plaincopy
  1. class PackageHandler extends Handler{
  2. *****************省略若干********************
  3. public void handleMessage(Message msg) {
  4. try {
  5. doHandleMessage(msg);
  6. } finally {
  7. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
  8. }
  9. }
  10. ******************省略若干**********************
  11. }

把信息发给doHandleMessage()方法,方法中用switch()语句进行判定传来Message

[java] view plaincopy
  1. void doHandleMessage(Message msg) {
  2. switch (msg.what) {
  3. case INIT_COPY: {
  4. if (DEBUG_SD_INSTALL) Log.i(TAG, "init_copy");
  5. HandlerParams params = (HandlerParams) msg.obj;
  6. int idx = mPendingInstalls.size();
  7. if (DEBUG_SD_INSTALL) Log.i(TAG, "idx=" + idx);
  8. // If a bind was already initiated we dont really
  9. // need to do anything. The pending install
  10. // will be processed later on.
  11. if (!mBound) {
  12. // If this is the only one pending we might
  13. // have to bind to the service again.
  14. if (!connectToService()) {
  15. Slog.e(TAG, "Failed to bind to media container service");
  16. params.serviceError();
  17. return;
  18. } else {
  19. // Once we bind to the service, the first
  20. // pending request will be processed.
  21. mPendingInstalls.add(idx, params);
  22. }
  23. } else {
  24. mPendingInstalls.add(idx, params);
  25. // Already bound to the service. Just make
  26. // sure we trigger off processing the first request.
  27. if (idx == 0) {
  28. mHandler.sendEmptyMessage(MCS_BOUND);
  29. }
  30. }
  31. break;
  32. }
  33. case MCS_BOUND: {
  34. if (DEBUG_SD_INSTALL) Log.i(TAG, "mcs_bound");
  35. if (msg.obj != null) {
  36. mContainerService = (IMediaContainerService) msg.obj;
  37. }
  38. if (mContainerService == null) {
  39. // Something seriously wrong. Bail out
  40. Slog.e(TAG, "Cannot bind to media container service");
  41. for (HandlerParams params : mPendingInstalls) {
  42. mPendingInstalls.remove(0);
  43. // Indicate service bind error
  44. params.serviceError();
  45. }
  46. mPendingInstalls.clear();
  47. } else if (mPendingInstalls.size() > 0) {
  48. HandlerParams params = mPendingInstalls.get(0);
  49. if (params != null) {
  50. params.startCopy();
  51. }
  52. } else {
  53. // Should never happen ideally.
  54. Slog.w(TAG, "Empty queue");
  55. }
  56. break;
  57. }
  58. ****************省略若干**********************
  59. }
  60. }

public final boolean sendMessage (Message msg)

public final boolean sendEmptyMessage (int what)

两者参数有别。

然后调用抽象类HandlerParams中的一个startCopy()方法

abstract class HandlerParams {

final void startCopy() {

***************若干if语句判定否这打回handler消息*******

handleReturnCode();

}
}

handleReturnCode()复写了两次其中有一次是删除时要调用的,只列出安装调用的一个方法

[java] view plaincopy
  1. @Override
  2. void handleReturnCode() {
  3. // If mArgs is null, then MCS couldn't be reached. When it
  4. // reconnects, it will try again to install. At that point, this
  5. // will succeed.
  6. if (mArgs != null) {
  7. processPendingInstall(mArgs, mRet);
  8. }
  9. }

这时可以清楚的看见 processPendingInstall()被调用。

其中run()方法如下

[java] view plaincopy
  1. run(){
  2. synchronized (mInstallLock) {
  3. ************省略*****************
  4. installPackageLI(args, true, res);
  5. }
  6. }
  7. instaPacakgeLI()args,res参数分析

-----------------------------------------------------------------------------------------

//InstallArgs 是在PackageService定义的static abstract class InstallArgs 静态抽象类。

[java] view plaincopy
  1. static abstract class InstallArgs {
  2. *********************************************************************
  3. 其中定义了flag标志,packageURL,创建文件,拷贝apk,修改包名称,
  4. 还有一些删除文件的清理,释放存储函数。
  5. *********************************************************************
  6. }
  7. class PackageInstalledInfo {
  8. String name;
  9. int uid;
  10. PackageParser.Package pkg;
  11. int returnCode;
  12. PackageRemovedInfo removedInfo;
  13. }

-----------------------------------------------------------------------------------------

[java] view plaincopy
  1. private void installPackageLI(InstallArgs args,
  2. boolean newInstall, PackageInstalledInfo res) {
  3. int pFlags = args.flags;
  4. String installerPackageName = args.installerPackageName;
  5. File tmpPackageFile = new File(args.getCodePath());
  6. boolean forwardLocked = ((pFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
  7. boolean onSd = ((pFlags & PackageManager.INSTALL_EXTERNAL) != 0);
  8. boolean replace = false;
  9. int scanMode = (onSd ? 0 : SCAN_MONITOR) | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE
  10. | (newInstall ? SCAN_NEW_INSTALL : 0);
  11. // Result object to be returned
  12. res.returnCode = PackageManager.INSTALL_SUCCEEDED;
  13. // Retrieve PackageSettings and parse package
  14. int parseFlags = PackageParser.PARSE_CHATTY |
  15. (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) |
  16. (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
  17. parseFlags |= mDefParseFlags;
  18. PackageParser pp = new PackageParser(tmpPackageFile.getPath());
  19. pp.setSeparateProcesses(mSeparateProcesses);
  20. final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,
  21. null, mMetrics, parseFlags);
  22. if (pkg == null) {
  23. res.returnCode = pp.getParseError();
  24. return;
  25. }
  26. String pkgName = res.name = pkg.packageName;
  27. if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
  28. if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {
  29. res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;
  30. return;
  31. }
  32. }
  33. if (GET_CERTIFICATES && !pp.collectCertificates(pkg, parseFlags)) {
  34. res.returnCode = pp.getParseError();
  35. return;
  36. }
  37. // Get rid of all references to package scan path via parser.
  38. pp = null;
  39. String oldCodePath = null;
  40. boolean systemApp = false;
  41. synchronized (mPackages) {
  42. // Check if installing already existing package
  43. if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
  44. String oldName = mSettings.mRenamedPackages.get(pkgName);
  45. if (pkg.mOriginalPackages != null
  46. && pkg.mOriginalPackages.contains(oldName)
  47. && mPackages.containsKey(oldName)) {
  48. // This package is derived from an original package,
  49. // and this device has been updating from that original
  50. // name.  We must continue using the original name, so
  51. // rename the new package here.
  52. pkg.setPackageName(oldName);
  53. pkgName = pkg.packageName;
  54. replace = true;
  55. } else if (mPackages.containsKey(pkgName)) {
  56. // This package, under its official name, already exists
  57. // on the device; we should replace it.
  58. replace = true;
  59. }
  60. }
  61. PackageSetting ps = mSettings.mPackages.get(pkgName);
  62. if (ps != null) {
  63. oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
  64. if (ps.pkg != null && ps.pkg.applicationInfo != null) {
  65. systemApp = (ps.pkg.applicationInfo.flags &
  66. ApplicationInfo.FLAG_SYSTEM) != 0;
  67. }
  68. }
  69. }
  70. if (systemApp && onSd) {
  71. // Disable updates to system apps on sdcard
  72. Slog.w(TAG, "Cannot install updates to system apps on sdcard");
  73. res.returnCode = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
  74. return;
  75. }
  76. if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {
  77. res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
  78. return;
  79. }
  80. // Set application objects path explicitly after the rename
  81. setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath());
  82. pkg.applicationInfo.nativeLibraryDir = args.getNativeLibraryPath();
  83. if (replace) {
  84. replacePackageLI(pkg, parseFlags, scanMode,
  85. installerPackageName, res);
  86. } else {
  87. installNewPackageLI(pkg, parseFlags, scanMode,
  88. installerPackageName,res);
  89. }
  90. }

最后判断如果以前不存在那么调用installNewPackageLI()

[java] view plaincopy
  1. private void installNewPackageLI(PackageParser.Package pkg,
  2. int parseFlags,int scanMode,
  3. String installerPackageName, PackageInstalledInfo res) {
  4. ***********************省略若干*************************************************
  5. PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,
  6. System.currentTimeMillis());
  7. ***********************省略若干**************************************************
  8. }

最后终于回到了和开机安装一样的地方.与开机方式安装调用统一方法。

三、从ADB工具安装

其入口函数源文件为pm.java

(源文件路径:android\frameworks\base\cmds\pm\src\com\android\commands\pm\pm.java)

其中\system\framework\pm.jar 包管理库

包管理脚本 \system\bin\pm 解析

showUsage就是使用方法

[java] view plaincopy
  1. private static void showUsage() {
  2. System.err.println("usage: pm [list|path|install|uninstall]");
  3. System.err.println("       pm list packages [-f]");
  4. System.err.println("       pm list permission-groups");
  5. System.err.println("       pm list permissions [-g] [-f] [-d] [-u] [GROUP]");
  6. System.err.println("       pm list instrumentation [-f] [TARGET-PACKAGE]");
  7. System.err.println("       pm list features");
  8. System.err.println("       pm path PACKAGE");
  9. System.err.println("       pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] PATH");
  10. System.err.println("       pm uninstall [-k] PACKAGE");
  11. System.err.println("       pm enable PACKAGE_OR_COMPONENT");
  12. System.err.println("       pm disable PACKAGE_OR_COMPONENT");
  13. System.err.println("       pm setInstallLocation [0/auto] [1/internal] [2/external]");
  14. **********************省略**************************
  15. }

安装时候会调用 runInstall()方法

[java] view plaincopy
  1. private void runInstall() {
  2. int installFlags = 0;
  3. String installerPackageName = null;
  4. String opt;
  5. while ((opt=nextOption()) != null) {
  6. if (opt.equals("-l")) {
  7. installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
  8. } else if (opt.equals("-r")) {
  9. installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
  10. } else if (opt.equals("-i")) {
  11. installerPackageName = nextOptionData();
  12. if (installerPackageName == null) {
  13. System.err.println("Error: no value specified for -i");
  14. showUsage();
  15. return;
  16. }
  17. } else if (opt.equals("-t")) {
  18. installFlags |= PackageManager.INSTALL_ALLOW_TEST;
  19. } else if (opt.equals("-s")) {
  20. // Override if -s option is specified.
  21. installFlags |= PackageManager.INSTALL_EXTERNAL;
  22. } else if (opt.equals("-f")) {
  23. // Override if -s option is specified.
  24. installFlags |= PackageManager.INSTALL_INTERNAL;
  25. } else {
  26. System.err.println("Error: Unknown option: " + opt);
  27. showUsage();
  28. return;
  29. }
  30. }
  31. String apkFilePath = nextArg();
  32. System.err.println("\tpkg: " + apkFilePath);
  33. if (apkFilePath == null) {
  34. System.err.println("Error: no package specified");
  35. showUsage();
  36. return;
  37. }
  38. PackageInstallObserver obs = new PackageInstallObserver();
  39. try {
  40. mPm.installPackage(Uri.fromFile(new File(apkFilePath)), obs, installFlags,
  41. installerPackageName);
  42. synchronized (obs) {
  43. while (!obs.finished) {
  44. try {
  45. obs.wait();
  46. } catch (InterruptedException e) {
  47. }
  48. }
  49. if (obs.result == PackageManager.INSTALL_SUCCEEDED) {
  50. System.out.println("Success");
  51. } else {
  52. System.err.println("Failure ["
  53. + installFailureToString(obs.result)
  54. + "]");
  55. }
  56. }
  57. } catch (RemoteException e) {
  58. System.err.println(e.toString());
  59. System.err.println(PM_NOT_RUNNING_ERR);
  60. }
  61. }

其中的

PackageInstallObserver obs = new PackageInstallObserver();

mPm.installPackage(Uri.fromFile(new File(apkFilePath)), obs, installFlags,

installerPackageName);

如果安装成功

obs.result == PackageManager.INSTALL_SUCCEEDED)

又因为有

IPackageManage mPm;

mPm = IpackageManager.Stub.asInterface(ServiceManager.getService("package"));

Stub是接口IPackageManage的静态抽象类,asInterface是返回IPackageManager代理的静态方法。

因为class PackageManagerService extends IPackageManager.Stub

所以mPm.installPackage 调用

/* Called when a downloaded package installation has been confirmed by the user */

public void installPackage(

final Uri packageURI, final IPackageInstallObserver observer, final int flags,final String installerPackageName)

这样就是从网络下载安装的入口了。

四,从SD卡安装

系统调用PackageInstallerActivity.java(/home/zhongda/androidSRC/vortex-8inch-for-hoperun/packages/apps/PackageInstaller/src/com/android/packageinstaller)

进入这个Activity会判断信息是否有错,然后调用

private void initiateInstall()判断是否曾经有过同名包的安装,或者包已经安装

通过后执行private void startInstallConfirm() 点击OK按钮后经过一系列的安装信息的判断Intent跳转到

[java] view plaincopy
  1. public class InstallAppProgress extends Activity implements View.OnClickListener, OnCancelListener
  2. public void onCreate(Bundle icicle) {
  3. super.onCreate(icicle);
  4. Intent intent = getIntent();
  5. mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
  6. mPackageURI = intent.getData();
  7. initView();
  8. }

方法中调用了initView()方法

[java] view plaincopy
  1. public void initView() {
  2. requestWindowFeature(Window.FEATURE_NO_TITLE);
  3. setContentView(R.layout.op_progress);
  4. int installFlags = 0;
  5. PackageManager pm = getPackageManager();
  6. try {
  7. PackageInfo pi = pm.getPackageInfo(mAppInfo.packageName,
  8. PackageManager.GET_UNINSTALLED_PACKAGES);
  9. if(pi != null) {
  10. installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
  11. }
  12. } catch (NameNotFoundException e) {
  13. }
  14. if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) {
  15. Log.w(TAG, "Replacing package:" + mAppInfo.packageName);
  16. }
  17. PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, mAppInfo,
  18. mPackageURI);
  19. mLabel = as.label;
  20. PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
  21. mStatusTextView = (TextView)findViewById(R.id.center_text);
  22. mStatusTextView.setText(R.string.installing);
  23. mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
  24. mProgressBar.setIndeterminate(true);
  25. // Hide button till progress is being displayed
  26. mOkPanel = (View)findViewById(R.id.buttons_panel);
  27. mDoneButton = (Button)findViewById(R.id.done_button);
  28. mLaunchButton = (Button)findViewById(R.id.launch_button);
  29. mOkPanel.setVisibility(View.INVISIBLE);
  30. String installerPackageName = getIntent().getStringExtra(
  31. Intent.EXTRA_INSTALLER_PACKAGE_NAME);
  32. PackageInstallObserver observer = new PackageInstallObserver();
  33. pm.installPackage(mPackageURI, observer, installFlags, installerPackageName);
  34. }

方法最后我们可以看到再次调用安装接口完成安装。

APK安装过程及原理详解相关推荐

  1. APK 安装过程 及 原理 详解

    APK为AndroidPackage的缩写 Android应用安装有如下四种方式: 1.系统应用安装――开机时完成,没有安装界面 2.网络下载应用安装――通过market应用完成,没有安装界面 3.A ...

  2. android备份:apk安装过程及原理,备份已安装应用的apk包技术实现方案

    安卓设备上备份已安装应用的apk包技术实现方案 需求的目的 在只有安装应用, 没有该应用的apk,而我们又想活取应用apk,用来分享给别人,或是应用的备份, 说是应用的增量升级的, 怎么办? 本文将告 ...

  3. 【转】Android中APK安装过程及原理解析

    应用安装是智能机的主要特点,即用户可以把各种应用(如游戏等)安装到手机上,并可以对其进行卸载等管理操作.APK是Android Package的缩写,即Android安装包.APK是类似Symbian ...

  4. Android中APK安装过程及原理解析

    来自华为内部资料 应用安装是智能机的主要特点,即用户可以把各种应用(如游戏等)安装到手机上,并可以对其进行卸载等管理操作.APK是Android Package的缩写,即android安装包.APK是 ...

  5. android照片涂抹功能,android:照片涂画功能实现过程及原理详解

    这个功能可以帮你实现,在图片上进行随意的涂抹,可以用于SNS产品. 绘图本身很简单,但是要实现在图片上指定的部分精确(位置,缩放)的绘图,就有点麻烦了. 下面讲讲实现过程及原理: UI构图这个UI,看 ...

  6. Android APK文件结构 完整打包编译的流程 APK安装过程 详解

    Android apk文件结构 打包编译的流程 Android官网 配置构建 流程 Configure your build The build process APK文件结构 assets res ...

  7. java源码系列:HashMap底层存储原理详解——4、技术本质-原理过程-算法-取模具体解决什么问题

    目录 简介 取模具体解决什么问题? 通过数组特性,推导ascii码计算出来的下标值,创建数组非常占用空间 取模,可保证下标,在HashMap默认创建下标之内 简介 上一篇文章,我们讲到 哈希算法.哈希 ...

  8. 【胖虎的逆向之路】02——Android整体加壳原理详解实现

    [胖虎的逆向之路](02)--Android整体加壳原理详解&实现 Android Apk的加壳原理流程及详解 文章目录 [胖虎的逆向之路](02)--Android整体加壳原理详解& ...

  9. nginx配置文件及工作原理详解

    nginx配置文件及工作原理详解 1 nginx配置文件的结构 2 nginx工作原理 1 nginx配置文件的结构 1)以下是nginx配置文件默认的主要内容: #user nobody; #配置用 ...

最新文章

  1. MATLAB语法基础
  2. sql server 连接工具_SQL on file 工具
  3. python有趣的小项目-这10个Python项目超有趣!
  4. 【数据挖掘知识点七】相关与回归分析
  5. caffe的prototxt文件
  6. 苹果ios鸿蒙,苹果iOS界面一直都很美,鸿蒙系统在这方面,让人担心
  7. python线程任务run_python线程、进程知识梳理
  8. 【实时+排重】摆脱渠道统计刷量作弊行为
  9. ubantu系统之快捷键使用
  10. 月份对比_行业洞察 | 10月份行业概览amp;头部广告主盘点
  11. NetBeans Weekly News: #125-Nov 17,2010
  12. Redhat7.5安装谷歌浏览器
  13. 计算请假时间(不算节假日)
  14. web移动开发总结(六)
  15. GeoServer中的WPS服务
  16. 信息安全-网站安全需求分析与安全保护工程(一)
  17. market Dwon
  18. CSS - 精灵图和字体图标
  19. 【第8天】SQL进阶-更新记录(SQL 小虚竹)
  20. 【UiPath2022+C#】UiPath控制流程概述

热门文章

  1. php漂亮按钮代码,分享一款金属感十足的按钮样式代码
  2. 宅在家里写数据库中MD5加密
  3. 机器学习——特征工程之特征选择
  4. Unsupported major.minor version 52.0解决
  5. 转:为什么说GAN很快就要替代现有摄影技术了?
  6. UVA10079 Pizza Cutting
  7. 腾讯云自曝自家技术只值1分钱,这技术以后谁还敢用
  8. 使用BULK COLLECT+FORALL加速批量提交
  9. ASP.NET MVC View使用Conditional compilation symbols
  10. 分析器错误信息: 未能加载类型命名空间.类...