APK安装过程分析_packageparser.parse_must_be_apk 这个是检查什么-程序员宅基地

技术标签: android  

应用程序包的安装是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包

 // Find base frameworks (resource packages without code).  
            mFrameworkInstallObserver = new AppDirObserver(  
                mFrameworkDir.getPath(), OBSERVER_EVENTS, true);  
            mFrameworkInstallObserver.startWatching();  
            scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM  
                    | PackageParser.PARSE_IS_SYSTEM_DIR,  
                    scanMode | SCAN_NO_DEX, 0);  

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

 // Collect all system packages.  
           mSystemAppDir = new File(Environment.getRootDirectory(), "app");  
           mSystemInstallObserver = new AppDirObserver(  
               mSystemAppDir.getPath(), OBSERVER_EVENTS, true);  
           mSystemInstallObserver.startWatching();  
           scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM  
                   | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);  

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

 // Collect all vendor packages.  
            mVendorAppDir = new File("/vendor/app");  
            mVendorInstallObserver = new AppDirObserver(  
                mVendorAppDir.getPath(), OBSERVER_EVENTS, true);  
            mVendorInstallObserver.startWatching();  
            scanDirLI(mVendorAppDir, PackageParser.PARSE_IS_SYSTEM  
                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);  

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

 scanDirLI(mAppInstallDir, 0, scanMode, 0);  

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

 scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,  
                     scanMode, 0);  

扫描方法的代码清单

 private void scanDirLI(File dir, int flags, int scanMode, long currentTime) {  
         String[] files = dir.list();  
         if (files == null) {  
             Log.d(TAG, "No files in app dir " + dir);  
             return;  
         }  
         if (false) {  
             Log.d(TAG, "Scanning app dir " + dir);  
         }  
         int i;  
         for (i=0; i<files.length; i++) {  
             File file = new File(dir, files[i]);  
             if (!isPackageFilename(files[i])) {  
                 // Ignore entries which are not apk's  
                 continue;  
             }  
             PackageParser.Package pkg = scanPackageLI(file,  
                     flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime);  
             // Don't mess around with apps in system partition.  
             if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&  
                     mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {  
                 // Delete the apk  
                 Slog.w(TAG, "Cleaning up failed install of " + file);  
                 file.delete();  
             }  
         }  
     }  

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

private PackageParser.Package scanPackageLI(File scanFile,

int parseFlags, int scanMode, long currentTime)

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

 if (mInstaller != null) {  
                     int ret = mInstaller.install(pkgName, useEncryptedFSDir,  pkg.applicationInfo.uid,pkg.applicationInfo.uid);  
                     if(ret < 0) {  
                         // Error from installer  
                         mLastScanError =    PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;  
                         return null;  
                     }  
                 }  

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()函数为安装程序入口。

 public void installPackage(  
            final Uri packageURI, final IPackageInstallObserver observer, final int flags,  
            final String installerPackageName) {  
        mContext.enforceCallingOrSelfPermission(  
                android.Manifest.permission.INSTALL_PACKAGES, null);  
        Message msg = mHandler.obtainMessage(INIT_COPY);  
        msg.obj = new InstallParams(packageURI, observer, flags,  
                installerPackageName);  
        mHandler.sendMessage(msg);  
    }  

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

 class PackageHandler extends Handler{  
                    
 *****************省略若干********************  
          public void handleMessage(Message msg) {  
             try {  
                 doHandleMessage(msg);  
             } finally {  
                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
             }  
         }  
    ******************省略若干**********************  
  }  

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

  void doHandleMessage(Message msg{  
             switch (msg.what) {  
              
                 case INIT_COPY: {  
                     if (DEBUG_SD_INSTALL) Log.i(TAG, "init_copy");  
                     HandlerParams params = (HandlerParams) msg.obj;  
                     int idx = mPendingInstalls.size();  
                     if (DEBUG_SD_INSTALL) Log.i(TAG, "idx=" + idx);  
                     // If a bind was already initiated we dont really  
                     // need to do anything. The pending install  
                     // will be processed later on.  
                     if (!mBound) {  
                         // 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();  
                             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;  
                 }  
                 case MCS_BOUND: {  
                     if (DEBUG_SD_INSTALL) Log.i(TAG, "mcs_bound");  
                     if (msg.obj != null) {  
                         mContainerService = (IMediaContainerService) msg.obj;  
                     }  
                     if (mContainerService == null) {  
                         // Something seriously wrong. Bail out  
                         Slog.e(TAG, "Cannot bind to media container service");  
                         for (HandlerParams params : mPendingInstalls) {  
                             mPendingInstalls.remove(0);  
                             // Indicate service bind error  
                             params.serviceError();  
                         }  
                         mPendingInstalls.clear();  
                     } else if (mPendingInstalls.size() > 0) {  
                         HandlerParams params = mPendingInstalls.get(0);  
                         if (params != null) {  
                             params.startCopy();  
                         }  
                     } else {  
                         // Should never happen ideally.  
                         Slog.w(TAG, "Empty queue");  
                     }  
                    break;  
                 }  
             ****************省略若干**********************  
 }  
 }               

public final boolean sendMessage (Message msg)

public final boolean sendEmptyMessage (int what)

两者参数有别。

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

abstract class HandlerParams {

final void startCopy() {

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

handleReturnCode();

}  
}

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

 @Override  
        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);  
            }  
       }  

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

其中run()方法如下

 run(){  
 synchronized (mInstallLock) {  
                         ************省略*****************  
                         installPackageLI(args, true, res);  
                      
  }  
 }  
 instaPacakgeLI()args,res参数分析  

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

 static abstract class InstallArgs {  
 *********************************************************************  
 其中定义了flag标志,packageURL,创建文件,拷贝apk,修改包名称,  
                     还有一些删除文件的清理,释放存储函数。  
     *********************************************************************  
 }  
   class PackageInstalledInfo {  
         String name;  
         int uid;  
         PackageParser.Package pkg;  
         int returnCode;  
         PackageRemovedInfo removedInfo;  
  }  

 private void installPackageLI(InstallArgs args,  
           boolean newInstall, PackageInstalledInfo res) {  
       int pFlags = args.flags;  
       String installerPackageName = args.installerPackageName;  
       File tmpPackageFile = new File(args.getCodePath());  
       boolean forwardLocked = ((pFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);  
       boolean onSd = ((pFlags & PackageManager.INSTALL_EXTERNAL) != 0);  
       boolean replace = false;  
       int scanMode = (onSd ? 0 : SCAN_MONITOR) | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE  
               | (newInstall ? SCAN_NEW_INSTALL : 0);  
       // Result object to be returned  
       res.returnCode = PackageManager.INSTALL_SUCCEEDED;  
       // Retrieve PackageSettings and parse package  
       int parseFlags = PackageParser.PARSE_CHATTY |  
       (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) |  
       (onSd ? PackageParser.PARSE_ON_SDCARD : 0);  
       parseFlags |= mDefParseFlags;  
       PackageParser pp = new PackageParser(tmpPackageFile.getPath());  
       pp.setSeparateProcesses(mSeparateProcesses);  
       final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,  
               null, mMetrics, parseFlags);  
       if (pkg == null) {  
           res.returnCode = pp.getParseError();  
           return;  
       }  
       String pkgName = res.name = pkg.packageName;  
       if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {  
           if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {  
               res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;  
               return;  
           }  
       }  
       if (GET_CERTIFICATES && !pp.collectCertificates(pkg, parseFlags)) {  
           res.returnCode = pp.getParseError();  
           return;  
       }  
       // Get rid of all references to package scan path via parser.  
       pp = null;  
       String oldCodePath = null;  
       boolean systemApp = false;  
       synchronized (mPackages) {  
           // Check if installing already existing package  
           if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0) {  
               String oldName = mSettings.mRenamedPackages.get(pkgName);  
               if (pkg.mOriginalPackages != null  
                       && pkg.mOriginalPackages.contains(oldName)  
                       && mPackages.containsKey(oldName)) {  
                   // This package is derived from an original package,  
                   // and this device has been updating from that original  
                   // name.  We must continue using the original name, so  
                   // rename the new package here.  
                   pkg.setPackageName(oldName);  
                   pkgName = pkg.packageName;  
                   replace = true;  
               } else if (mPackages.containsKey(pkgName)) {  
                   // This package, under its official name, already exists  
                   // on the device; we should replace it.  
                   replace = true;  
               }  
           }  
           PackageSetting ps = mSettings.mPackages.get(pkgName);  
          if (ps != null) {  
               oldCodePath = mSettings.mPackages.get(pkgName).codePathString;  
               if (ps.pkg != null && ps.pkg.applicationInfo != null) {  
                   systemApp = (ps.pkg.applicationInfo.flags &  
                           ApplicationInfo.FLAG_SYSTEM) != 0;  
               }  
           }  
       }  
       if (systemApp && onSd) {  
           // Disable updates to system apps on sdcard  
           Slog.w(TAG, "Cannot install updates to system apps on sdcard");  
           res.returnCode = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;  
           return;  
       }  
       if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {  
           res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;  
           return;  
       }  
       // Set application objects path explicitly after the rename  
       setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath());  
       pkg.applicationInfo.nativeLibraryDir = args.getNativeLibraryPath();  
       if (replace) {  
           replacePackageLI(pkg, parseFlags, scanMode,  
                   installerPackageName, res);  
       } else {  
           installNewPackageLI(pkg, parseFlags, scanMode,  
                   installerPackageName,res);  
       }  
   }  

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

 private void installNewPackageLI(PackageParser.Package pkg,  
             int parseFlags,int scanMode,  
             String installerPackageName, PackageInstalledInfo res) {  
      ***********************省略若干*************************************************  
         PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,  
                System.currentTimeMillis());  
      ***********************省略若干**************************************************    
 }  

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

三、从ADB工具安装 

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

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

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

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

showUsage就是使用方法

private static void showUsage() {   
        System.err.println("usage: pm [list|path|install|uninstall]");   
        System.err.println("       pm list packages [-f]");   
        System.err.println("       pm list permission-groups");   
        System.err.println("       pm list permissions [-g] [-f] [-d] [-u] [GROUP]");   
        System.err.println("       pm list instrumentation [-f] [TARGET-PACKAGE]");   
        System.err.println("       pm list features");   
        System.err.println("       pm path PACKAGE");   
        System.err.println("       pm install [-l] [-r] [-t] [-i INSTALLER_PACKAGE_NAME] [-s] [-f] PATH");   
        System.err.println("       pm uninstall [-k] PACKAGE");   
        System.err.println("       pm enable PACKAGE_OR_COMPONENT");   
        System.err.println("       pm disable PACKAGE_OR_COMPONENT");   
        System.err.println("       pm setInstallLocation [0/auto] [1/internal] [2/external]");  
      **********************省略**************************  
   }

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

private void runInstall() {  
      int installFlags = 0;  
      String installerPackageName = null;  
      String opt;  
      while ((opt=nextOption()) != null) {  
          if (opt.equals("-l")) {  
              installFlags |= PackageManager.INSTALL_FORWARD_LOCK;  
          } else if (opt.equals("-r")) {  
              installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;  
          } else if (opt.equals("-i")) {  
              installerPackageName = nextOptionData();  
              if (installerPackageName == null) {  
                  System.err.println("Error: no value specified for -i");  
                  showUsage();  
                  return;  
              }  
          } else if (opt.equals("-t")) {  
              installFlags |= PackageManager.INSTALL_ALLOW_TEST;  
          } else if (opt.equals("-s")) {  
              // Override if -s option is specified.  
              installFlags |= PackageManager.INSTALL_EXTERNAL;  
          } else if (opt.equals("-f")) {  
              // Override if -s option is specified.  
              installFlags |= PackageManager.INSTALL_INTERNAL;  
          } else {  
              System.err.println("Error: Unknown option: " + opt);  
              showUsage();  
              return;  
          }  
      }  
      String apkFilePath = nextArg();  
      System.err.println("\tpkg: " + apkFilePath);  
      if (apkFilePath == null) {  
          System.err.println("Error: no package specified");  
          showUsage();  
          return;  
      }  
      PackageInstallObserver obs = new PackageInstallObserver();  
      try {  
          mPm.installPackage(Uri.fromFile(new File(apkFilePath)), obs, installFlags,  
                  installerPackageName);  
          synchronized (obs) {  
              while (!obs.finished) {  
                  try {  
                      obs.wait();  
                  } catch (InterruptedException e) {  
                  }  
              }  
              if (obs.result == PackageManager.INSTALL_SUCCEEDED) {  
                  System.out.println("Success");  
              } else {  
                  System.err.println("Failure ["  
                          + installFailureToString(obs.result)  
                          + "]");  
              }  
          }  
      } catch (RemoteException e) {  
          System.err.println(e.toString());  
          System.err.println(PM_NOT_RUNNING_ERR);  
      }  
  }

其中的   

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跳转到

public class InstallAppProgress extends Activity implements View.OnClickListener, OnCancelListener  
   public void onCreate(Bundle icicle) {  
        super.onCreate(icicle);  
        Intent intent = getIntent();  
        mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);  
        mPackageURI = intent.getData();  
        initView();  
    } 

方法中调用了initView()方法

public void initView() {  
       requestWindowFeature(Window.FEATURE_NO_TITLE);  
       setContentView(R.layout.op_progress);  
       int installFlags = 0;  
       PackageManager pm = getPackageManager();  
       try {  
           PackageInfo pi = pm.getPackageInfo(mAppInfo.packageName,   
                   PackageManager.GET_UNINSTALLED_PACKAGES);  
           if(pi != null) {  
               installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;  
           }  
       } catch (NameNotFoundException e) {  
       }  
       if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) {  
           Log.w(TAG, "Replacing package:" + mAppInfo.packageName);  
       }  
       PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, mAppInfo,  
               mPackageURI);  
       mLabel = as.label;  
       PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);  
       mStatusTextView = (TextView)findViewById(R.id.center_text);  
       mStatusTextView.setText(R.string.installing);  
       mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);  
       mProgressBar.setIndeterminate(true);  
       // Hide button till progress is being displayed  
       mOkPanel = (View)findViewById(R.id.buttons_panel);  
       mDoneButton = (Button)findViewById(R.id.done_button);  
       mLaunchButton = (Button)findViewById(R.id.launch_button);  
       mOkPanel.setVisibility(View.INVISIBLE);  
       String installerPackageName = getIntent().getStringExtra(  
               Intent.EXTRA_INSTALLER_PACKAGE_NAME);  
       PackageInstallObserver observer = new PackageInstallObserver();  
       pm.installPackage(mPackageURI, observer, installFlags, installerPackageName);  
   }
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/keranCSDN/article/details/53736812

智能推荐

前端开发之vue-grid-layout的使用和实例-程序员宅基地

文章浏览阅读1.1w次,点赞7次,收藏34次。vue-grid-layout的使用、实例、遇到的问题和解决方案_vue-grid-layout

Power Apps-上传附件控件_powerapps点击按钮上传附件-程序员宅基地

文章浏览阅读218次。然后连接一个数据源,就会在下面自动产生一个添加附件的组件。把这个控件复制粘贴到页面里,就可以单独使用来上传了。插入一个“编辑”窗体。_powerapps点击按钮上传附件

C++ 面向对象(Object-Oriented)的特征 & 构造函数& 析构函数_"object(cnofd[\"ofdrender\"])十条"-程序员宅基地

文章浏览阅读264次。(1) Abstraction (抽象)(2) Polymorphism (多态)(3) Inheritance (继承)(4) Encapsulation (封装)_"object(cnofd[\"ofdrender\"])十条"

修改node_modules源码,并保存,使用patch-package打补丁,git提交代码后,所有人可以用到修改后的_修改 node_modules-程序员宅基地

文章浏览阅读133次。删除node_modules,重新npm install看是否成功。在 package.json 文件中的 scripts 中加入。修改你的第三方库的bug等。然后目录会多出一个目录文件。_修改 node_modules

【】kali--password:su的 Authentication failure问题,&sudo passwd root输入密码时Sorry, try again._password: su: authentication failure-程序员宅基地

文章浏览阅读883次。【代码】【】kali--password:su的 Authentication failure问题,&sudo passwd root输入密码时Sorry, try again._password: su: authentication failure

整理5个优秀的微信小程序开源项目_微信小程序开源模板-程序员宅基地

文章浏览阅读1w次,点赞13次,收藏97次。整理5个优秀的微信小程序开源项目。收集了微信小程序开发过程中会使用到的资料、问题以及第三方组件库。_微信小程序开源模板

随便推点

Centos7最简搭建NFS服务器_centos7 搭建nfs server-程序员宅基地

文章浏览阅读128次。Centos7最简搭建NFS服务器_centos7 搭建nfs server

Springboot整合Mybatis-Plus使用总结(mybatis 坑补充)_mybaitis-plus ruledataobjectattributemapper' and '-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏3次。前言mybatis在持久层框架中还是比较火的,一般项目都是基于ssm。虽然mybatis可以直接在xml中通过SQL语句操作数据库,很是灵活。但正其操作都要通过SQL语句进行,就必须写大量的xml文件,很是麻烦。mybatis-plus就很好的解决了这个问题。..._mybaitis-plus ruledataobjectattributemapper' and 'com.picc.rule.management.d

EECE 1080C / Programming for ECESummer 2022 Laboratory 4: Global Functions Practice_eece1080c-程序员宅基地

文章浏览阅读325次。EECE 1080C / Programming for ECESummer 2022Laboratory 4: Global Functions PracticePlagiarism will not be tolerated:Topics covered:function creation and call statements (emphasis on global functions)Objective:To practice program development b_eece1080c

洛谷p4777 【模板】扩展中国剩余定理-程序员宅基地

文章浏览阅读53次。被同机房早就1年前就学过的东西我现在才学,wtcl。设要求的数为\(x\)。设当前处理到第\(k\)个同余式,设\(M = LCM ^ {k - 1} _ {i - 1}\) ,前\(k - 1\)个的通解就是\(x + i * M\)。那么其实第\(k\)个来说,其实就是求一个\(y\)使得\(x + y * M ≡ a_k(mod b_k)\)转化一下就是\(y * M ...

android 退出应用没有走ondestory方法,[Android基础论]为何Activity退出之后,系统没有调用onDestroy方法?...-程序员宅基地

文章浏览阅读1.3k次。首先,问题是如何出现的?晚上复查代码,发现一个activity没有调用自己的ondestroy方法我表示非常的费解,于是我检查了下代码。发现再finish代码之后接了如下代码finish();System.exit(0);//这就是罪魁祸首为什么这样写会出现问题System.exit(0);////看一下函数的原型public static void exit (int code)//Added ..._android 手动杀死app,activity不执行ondestroy

SylixOS快问快答_select函数 导致堆栈溢出 sylixos-程序员宅基地

文章浏览阅读894次。Q: SylixOS 版权是什么形式, 是否分为<开发版税>和<运行时版税>.A: SylixOS 是开源并免费的操作系统, 支持 BSD/GPL 协议(GPL 版本暂未确定). 没有任何的运行时版税. 您可以用她来做任何 您喜欢做的项目. 也可以修改 SylixOS 的源代码, 不需要支付任何费用. 当然笔者希望您可以将使用 SylixOS 开发的项目 (不需要开源)或对 SylixOS 源码的修改及时告知笔者.需要指出: SylixOS 本身仅是笔者用来提升自己水平而开发的_select函数 导致堆栈溢出 sylixos

推荐文章

热门文章

相关标签