码迷,mamicode.com
首页 > 其他好文 > 详细

深入理解PackageManagerService

时间:2015-06-12 13:29:24      阅读:409      评论:0      收藏:0      [点我收藏+]

标签:



PackageManagerService负责管理系统的Package,包括APK的安装,卸载,信息的查询等等。它的功能非常的多,也非常的强大,所以要重点分析。
PMS(PackageManagerService)和java中其他系统服务一样,也是一个Service,它和它的Client的关系:
技术分享

技术分享
1 IPackageManager接口定义了server要提供的业务函数,其中子类Stub继承Binder且实现了IPackageManager接口
2 PMS继承Stub,所以可以作为Server与Binder通信
3 Stub中的一个内部类Proxy中有一个IBinder的成员变量mRemote,利用mRemote可以和Server端通信
4 client端在使用的时候是使用Context.getPackageManager函数返回的ApplicationPackageManager对象来处理,ApplicationPackageManager内部成员变量mPM指向Proxy类型的对象

可以看出java层的系统服务的模型都是一样的,换的仅仅是服务和服务的实现,关于java层系统服务,请看java层系统服务在binder中的实现

8.1 PMS的启动
PMS的启动非常的复杂,涉及到Setting对象,属性系统,Installer系统,PackageHandler,系统权限,AndroidManifest.xml,Resouce,FileObserver已经APK的安装包的扫面等等,具体来说就是下面的历程:
技术分享
技术分享
其中ServerThread的启动之前已经讨论过了,这里直接看PackageManagerService的main函数 :   
 public static final IPackageManager main(Context context, boolean factoryTest,
            boolean onlyCore) {        
        PackageManagerService m = new PackageManagerService(context, factoryTest, onlyCore);
        ServiceManager.addService("package", m);//添加java系统服务的功能
        return m;
    }
因为添加服务到ServiceManager在之前的PowerManagerService中已经讨论过,这里不做介绍,这里只看PackageManagerService的创建,它的构造函数中做了很多的工作,这也是android启动慢的一个主要原因,分段来看这个函数的执行。

8.1.1 Setting对象的创建和初始化
....
mSettings = new Settings();
        mSettings.addSharedUserLPw("android.uid.system",
                Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM);
        mSettings.addSharedUserLPw("android.uid.phone",
                MULTIPLE_APPLICATION_UIDS
                        ? RADIO_UID : FIRST_APPLICATION_UID,
                ApplicationInfo.FLAG_SYSTEM);
        mSettings.addSharedUserLPw("android.uid.log",
                MULTIPLE_APPLICATION_UIDS
                        ? LOG_UID : FIRST_APPLICATION_UID,
                ApplicationInfo.FLAG_SYSTEM);
        mSettings.addSharedUserLPw("android.uid.nfc",
                MULTIPLE_APPLICATION_UIDS
                        ? NFC_UID : FIRST_APPLICATION_UID,
                ApplicationInfo.FLAG_SYSTEM);

这段代码做了两个工作: 1 构造Settings对象 ;2 调用addSharedUserLPw函数添加共享ID
1 构造Settings对象
Settings类在frameworks/base/services/java/com/android/server/pm中,Settings类的作用是管理android系统运行过程中的一些配置信息,它的构造函数: 
Settings() {
        File dataDir = Environment.getDataDirectory();
        File systemDir = new File(dataDir, "system");
        systemDir.mkdirs();
        FileUtils.setPermissions(systemDir.toString(),
                FileUtils.S_IRWXU|FileUtils.S_IRWXG
                |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                -1, -1);
        //packages.xml 记录系统所有安装apk的信息
        mSettingsFilename = new File(systemDir, "packages.xml");
  
         //packages-backup.xml 是packages.xml备份文件,在安装或者卸载apk是更新packages.xml文件就会用backup备份
        mBackupSettingsFilename = new File(systemDir, "packages-backup.xml");

        //packages.list 所有已安装apk的简要信息
        mPackageListFilename = new File(systemDir, "packages.list");

        //  packages-stopped.xml  强制stop的apk信息
        mStoppedPackagesFilename = new File(systemDir, "packages-stopped.xml");

         //packages-stopped.xml的备份文件 
        mBackupStoppedPackagesFilename = new File(systemDir, "packages-stopped-backup.xml");
    }
它的构造函数还是很简单的,初始化几个全局变量文件,这些文件变量后面解析过程中会用到。

2 调用addSharedUserLPw函数添加共享ID
 //定义了一个HashMap存储sharedUsers信息
 final HashMap<String, SharedUserSetting> mSharedUsers =
            new HashMap<String, SharedUserSetting>();

    SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) {
        //先在mSharedUsers中找有没有name对应的value。
        SharedUserSetting s = mSharedUsers.get(name);//
        if (s != null) {
            if (s.userId == uid) {//和传入的uid一样就直接返回
                return s;
            }
            ..如果s.userId != ui ,返回null
            return null;
        }
       
        //如果没有在mSharedUsers 找到该name对应的value值,则创建一个新的SharedUserSetting 对象
        s = new SharedUserSetting(name, pkgFlags);
        s.userId = uid;
        //调用addUserIdLPw 
        if (addUserIdLPw(uid, s, name)) {
            mSharedUsers.put(name, s);//添加到mSharedUsers  中
            return s;//返回创建的SharedUserSetting 对象 
        }
        return null;//addUserIdLPw 添加失败就返回null
    }
这里面又涉及到一个SharedUserSetting类,它的用途和AndroidManifest.xml文件中的一个标签相关android:sharedUserId 
如android:sharedUserId="android.uid.system",如果在AndroidManifest.xml中这么配置的话,它有一下几个作用:1 有相同android:sharedUserId值的apk之间可以运行在同一个进程中 ;2 通过设置android:sharedUserId,该apk所在的进程的uid就是android:sharedUserId的uid,这里就是apk有system用户的权限。也即是这个进程现在运行在sharedUserId所在进程中,且有它的权限。
现在要存储某一个UID有哪些apk共享该UID需要以下三个字段:
1 String name:就是存储xml文件中android:sharedUserId的值,这里就是"android.uid.system"
2 int uid:在linux中uid是一个整数,这里对应的就是1000
3 HashSet<PackageSetting> packages:共享同一个UID的package很多 ,package设置就是通过PackageSetting类实现,它的超类是GrantedPermissions,它的pkgFlage就是用来存储package的标记。
这里涉及到Settings、GrantedPermissions、ShareUserSetting等类,他们之间的关系如下:
技术分享
技术分享
1.Settings类中有一个HashMap<String,SharedUserSetting>的成员变量mSharedUsers,其中key为String(比如 "android.uid.system")类型,它表示某一个共享用户name,value类型就是
SharedUserSetting类型,用来存储该共享该name的所有共享用户信息。
2.SharedUserSetting类继承GrantedPermissions,且内部维护一个HashSet<PackageSetting>的成员变量packages,表示有相同userId的package设置信息接着看addUserIdLPw
3.其中Settings中有两个成员变量ArrayList<Object>mUserIds,和SparseArray<Object> mOtherUserIds,其中mUserIds存储大于等于(10000)的SharedUserSetting对象,而mOtherUserIds存储小于10000的SharedUserSetting对象。下面会分析。

addSharedUserLPw方法中调用了addUserIdLPw方法,这里以 mSettings.addSharedUserLPw("android.uid.system",Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM);为例,其中Process.SYSTEM_UID的值为1000
   
 private boolean addUserIdLPw(int uid, Object obj, Object name) {
        // 三个参数分别为 uid=1000
        // obj= new SharedUserSetting("android.uid.system",ApplicationInfo.FLAG_SYSTEM), 
        // name = "android.uid.system"

       //  PackageManagerService.FIRST_APPLICATION_UID值为 10000
        //PackageManagerService.MAX_APPLICATION_UIDS 值1000
        //这里不能uid不能超过10000+1000=11000
        //系统版本不一样这里的实现不一样
        if (uid >= PackageManagerService.FIRST_APPLICATION_UID + PackageManagerService.MAX_APPLICATION_UIDS) {
            return false;
        }
        
        //uid>=10000时
        if (uid >= PackageManagerService.FIRST_APPLICATION_UID) {
            int N = mUserIds.size();
            
            final int index = uid - PackageManagerService.FIRST_APPLICATION_UID;
            while (index >= N) {
                mUserIds.add(null);
                N++;
            }
            //可以看出mUserIds 中第i个位置,它存放的是uid=PackageManagerService.FIRST_APPLICATION_UID +i的SharedUserSetting 对象,如果没
            //有就设为null。
            if (mUserIds.get(index) != null) {//该位置已经设置过了
               return false;//返回false
            }
            mUserIds.set(index, obj);//该位置为null,可以插入,返回true
        } else {
            //uid<10000时 
            if (mOtherUserIds.get(uid) != null) {同理这里也是只不过是存储小于10000的SharedUserSetting 对象          
                return false;
            }
            mOtherUserIds.put(uid, obj);
        }
        return true;
    }
所以addUserIdLPw函数就是判断指定的uid能否按照一定的规则插入到mUserIds或者mOtherUserIds中。
如果这个方法返回true,按照key="mSharedUsers"     value=new SharedUserSetting("android.uid.system",ApplicationInfo.FLAG_SYSTEM)    put到Settings的成员变量mSharedUsers中。
到此为止Settting初始化和addSharedUserLPw分析完毕

8.1.2 获取系统默认设置
这里比较简单,读取"ro.build.type"和"debug.separate_processes"进行一些设置


8.1.3 启动HandlerThread
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper());
其中涉及到了HandlerThread、PackageHandler等类,它们之间的关系就是:
技术分享
技术分享
可以看出HandlerThread和PackageHandler分别代表系统通信模型中的Looper线程和Handler处理器,其中PackageHandler是PMS的内部类,它的handleMessage方法的实现实际调用了doHandleMessage方法: 
public void handleMessage(Message msg) {
            try {
                doHandleMessage(msg);//调用了doHandleMessage方法
            } finally {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            }
        }
 void doHandleMessage(Message msg) {
            switch (msg.what) {
          case INIT_COPY:
          ......
          case CHECK_PENDING_VERIFICATION:
           case PACKAGE_VERIFIED:

}
}
这里的作用就是启动mHandlerThread,不断从消息队列中取出来消息,交给PackageHandler处理。后面的apk安装会涉及到这部分的功能。

8.2.4 初始化UserManager
 mInstaller = new Installer();
 mUserAppDataDir = new File(dataDir, "user");
 mUserManager = new UserManager(mInstaller, mUserAppDataDir);
创建UserManager对象,看它的构造方法: 
 public UserManager(Installer installer, File baseUserPath) {
        this(Environment.getDataDirectory(), baseUserPath);
        mInstaller = installer;
    }

 UserManager(File dataDir, File baseUserPath) {
        //USER_INFO_DIR 为"system/users";
        mUsersDir = new File(dataDir, USER_INFO_DIR);
        //创建/data/system/users 目录
        mUsersDir.mkdirs();
        mBaseUserPath = baseUserPath;
        FileUtils.setPermissions(mUsersDir.toString(),
                FileUtils.S_IRWXU|FileUtils.S_IRWXG
                |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                -1, -1);
        // 指向/data/system/users userlist.xml文件
        mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
        readUserList();//调用readUserList函数
    }
先创建了/data/system/users 目录 ,然后调用readUserList 函数
   
 private void readUserList() {
        mUsers = new SparseArray<UserInfo>();     
          //如果/data/system/users userlist.xml文件 不存在
        if (!mUserListFile.exists()) {
            fallbackToSingleUser();
            return;
        }
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(mUserListFile);
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(fis, null);
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG
                    && type != XmlPullParser.END_DOCUMENT) {
                ;
            }

              while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
               //TAG_USER 为"user"
                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
                    //读取<user>标签中的id属性
                    String id = parser.getAttributeValue(null, ATTR_ID);
                    UserInfo user = readUser(Integer.parseInt(id));//根据id在读取xml构造成一个UserInfo实体
                    if (user != null) {
                        mUsers.put(user.id, user);//添加到mUsers 
                    }
                }
            }
            updateUserIds();
        } catch (IOException ioe) {
            fallbackToSingleUser();
        } catch (XmlPullParserException pe) {
            fallbackToSingleUser();
        }
    }
其中涉及到了readUser,它的作用就是读取/data/system/users/id.xml文件,将xml文件转化为UserInfo实体类
 private UserInfo readUser(int id) {
        int flags = 0;
        String name = null;

        FileInputStream fis = null;
        try {
            File userFile = new File(mUsersDir, Integer.toString(id) + ".xml");
            fis = new FileInputStream(userFile);
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(fis, null);
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG
                    && type != XmlPullParser.END_DOCUMENT) {
                ;
            }

            if (type != XmlPullParser.START_TAG) {
                Slog.e(LOG_TAG, "Unable to read user " + id);
                return null;
            }

            if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
                String storedId = parser.getAttributeValue(null, ATTR_ID);
                if (Integer.parseInt(storedId) != id) {
                    Slog.e(LOG_TAG, "User id does not match the file name");
                    return null;
                }
                String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
                flags = Integer.parseInt(flagString);

                while ((type = parser.next()) != XmlPullParser.START_TAG
                        && type != XmlPullParser.END_DOCUMENT) {
                }
                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) {
                    type = parser.next();
                    if (type == XmlPullParser.TEXT) {
                        name = parser.getText();
                    }
                }
            }
            fis.close();

            UserInfo userInfo = new UserInfo(id, name, flags);
            return userInfo;

        } catch (IOException ioe) {
        } catch (XmlPullParserException pe) {
        }
        return null;
}
看一下UserInfo实体类    
public class UserInfo implements Parcelable {
     public static final int FLAG_PRIMARY = 0x00000001;

   public static final int FLAG_ADMIN   = 0x00000002;
 public static final int FLAG_GUEST   = 0x00000004;

   public int id;//用户id
    public String name;//用户名称
    public int flags;//用户标记,是primary,admin,guest等等

    public UserInfo(int id, String name, int flags) {
        this.id = id;
        this.name = name;
        this.flags = flags;
    }

 public boolean isPrimary() {
        return (flags & FLAG_PRIMARY) == FLAG_PRIMARY;
    }

    public boolean isAdmin() {
        return (flags & FLAG_ADMIN) == FLAG_ADMIN;
    }

    public boolean isGuest() {
        return (flags & FLAG_GUEST) == FLAG_GUEST;
    }
 public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int parcelableFlags) {
        dest.writeInt(id);
        dest.writeString(name);
        dest.writeInt(flags);
    }

    public static final Parcelable.Creator<UserInfo> CREATOR
            = new Parcelable.Creator<UserInfo>() {
        public UserInfo createFromParcel(Parcel source) {
            return new UserInfo(source);
        }
        public UserInfo[] newArray(int size) {
            return new UserInfo[size];
        }
    };

    private UserInfo(Parcel source) {
        id = source.readInt();
        name = source.readString();
        flags = source.readInt();
    }
}
实现了Parcelable 接口保存user的一些信息。
如果readUser(id)的结果不为null就将该结果加入到mUsers中。

8.2.5 解析permission
readPermissions();函数调用的实现
   
void readPermissions() {
        //  指向etc/permissions 文件
        File libraryDir = new File(Environment.getRootDirectory(), "etc/permissions");
         ..

        // Iterate over the files in the directory and scan .xml files
        for (File f : libraryDir.listFiles()) {
            //不处理etc/permissions/platform.xml 
            if (f.getPath().endsWith("etc/permissions/platform.xml")) {
                continue;
            }
            //处理.xml文件
            if (!f.getPath().endsWith(".xml")) {
               
                continue;
            }
            //处理刻度的
            if (!f.canRead()) {
               
                continue;
            }

            readPermissionsFromXml(f);
        }

        // Read permissions from .../etc/permissions/platform.xml last so it will take precedence
          最后解析etc/permissions/platform.xm 文件
        final File permFile = new File(Environment.getRootDirectory(),"etc/permissions/platform.xml");
        readPermissionsFromXml(permFile);
    }
可见就是解析各种/etc/permissions目录中的xml文件,最后才解析etc/permissions/platform.xml文件。不过我们先看etc/permissions/platform.xml文件,这是我手机上的platform.xml文件(一部分)
<permissions>
<!--为指定group 的gid分配相应的权限--> 
<!--为指定gid为net_bt_admin分配 BLUETOOTH_ADMIN权限--> 
 <permission name="android.permission.BLUETOOTH_ADMIN" >
        <group gid="net_bt_admin" />
    </permission>

    <permission name="android.permission.BLUETOOTH" >
        <group gid="net_bt" />
    </permission>

    <permission name="android.permission.BLUETOOTH_STACK" >
        <group gid="net_bt_stack" />
    </permission>

<!--为指定的uid分配相应的权限--> 
<!--为指定uid为  shell 分配WRITE_EXTERNAL_STORAGE权限-->  
   <assign-permission name="android.permission.WRITE_EXTERNAL_STORAGE" uid="shell" />
    <assign-permission name="android.permission.SEND_SMS" uid="shell" />
    <assign-permission name="android.permission.CALL_PHONE" uid="shell" />
    <assign-permission name="android.permission.READ_CONTACTS" uid="shell" />
    <assign-permission name="android.permission.WRITE_CONTACTS" uid="shell" />

<!--连接库-->
 <library name="android.test.runner" file="/system/framework/android.test.runner.jar" />
    <library name="javax.obex" file="/system/framework/javax.obex.jar"/>

</permissions>
解析platform.xml通过函数readPermissionsFromXml来实现,该函数的主要作用就是将permission 、group 、assign-permission,library 标签读出来并存储在相应的数据结构中。

上面的文件是platform.xml文件,还有一类文件是feature配置文件,包括硬件和软件 feature配置,先看硬件feature,这里android.hardware.wifi.xml为例:
<permissions>
    <feature name="android.hardware.wifi" />
</permissions>
说明当前设备可以支持wifi
再看软件 feature配置android.software.live_wallpaper.xml
permissions>
    <feature name="android.software.live_wallpaper" />
</permissions>

8.2.6 解析package文件
mSettings.readLPw()方法,它的作用就是解析之前的packagexxx.xml文件,包括package.xml,package-backup.xml,package-stop.xml,package-stopback.xml这些文件,主要历程如下:
技术分享

主要历程在第二部分解析packages.xml文件,保存到相应的数据结构之中。


8.2.7 dexopt优化
 int scanMode = SCAN_MONITOR | SCAN_NO_PATHS | SCAN_DEFER_DEX;
            if (mNoDexOpt) {                
                scanMode |= SCAN_NO_DEX;//不需要DEX优化
            }
            final HashSet<String> libFiles = new HashSet<String>();
            mFrameworkDir = new File(Environment.getRootDirectory(), "framework");
            mDalvikCacheDir = new File(dataDir, "dalvik-cache");
            boolean didDexOpt = false;
            
            String bootClassPath = System.getProperty("java.boot.class.path");
            if (bootClassPath != null) {
                String[] paths = splitString(bootClassPath, ':');
                for (int i=0; i<paths.length; i++) {
                    try {
                         //判断是否需要dex优化,如果需要则加入到libFiles中
                        if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) {
                            libFiles.add(paths[i]);
                            mInstaller.dexopt(paths[i], Process.SYSTEM_UID, true);
                            didDexOpt = true;
                        }
                    } catch (FileNotFoundException e) {
                        
                    } catch (IOException e) {
                      
                    }
                }
            } else {
              
            }

             //在platform.xml文件中存储library  标签的变量
            if (mSharedLibraries.size() > 0) {
                Iterator<String> libs = mSharedLibraries.values().iterator();
                while (libs.hasNext()) {
                    String lib = libs.next();
                    try {
                         //判断是否需要dex优化
                        if (dalvik.system.DexFile.isDexOptNeeded(lib)) {
                            libFiles.add(lib);
                            mInstaller.dexopt(lib, Process.SYSTEM_UID, true);
                            didDexOpt = true;
                        }
                    } catch (FileNotFoundException e) {
                         
                    } catch (IOException e) {
                         
                    }
                }
            }

  //对于framework-res.apk直接添加到libFiles中
 libFiles.add(mFrameworkDir.getPath() + "/framework-res.apk");
//mFrameworkDir代表 /system/frameworks目录,优化里面的apk和jar文件
String[] frameworkFiles = mFrameworkDir.list();
            if (frameworkFiles != null) {
                for (int i=0; i<frameworkFiles.length; i++) {
                 .....  
                }
            }

            if (didDexOpt) {//如果didDexOpt为true,则说明优化成功      
               //mDalvikCacheDir代表      /data/dalvik-cache  
                String[] files = mDalvikCacheDir.list();
                if (files != null) {
                    for (int i=0; i<files.length; i++) {
                        String fn = files[i];
                         //删除data@app@ 或者data@app-private@的文件
                          if (fn.startsWith("data@app@")|| fn.startsWith("data@app-private@")) {                           
                            (new File(mDalvikCacheDir, fn)).delete();//删除缓存数据
                        }
                    }
                }
            }
主要是对几个路径的apk和jar文件判断看是否需要dexopt优化,有java.boot.class.path、mSharedLibraries、/system/frameworks目录这几个目录下面机型操作,若有任意一个文件夹中有任意一个文件需要优化,删除 /data/dalvik-cache  目录下以data@app@或者data@app-private@文件

8.2.8 启动FileObserver监控APK文件目录
           
 <span style="white-space:pre">	</span>    //监控 system/framework目录
            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);
            
           //监控 system/app目录 
            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);
            
             //监控 /vendor/app目录  
            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);

            mInstaller.moveFiles();

            ...            
            mAppInstallDir = new File(dataDir, "app");
            //look for any incomplete package installations
            ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr();
            //clean up list
            for(int i = 0; i < deletePkgsList.size(); i++) {               
                cleanupInstallFailedPackage(deletePkgsList.get(i));
            }
          
            deleteTempPackageFiles();

            if (!mOnlyCore) {
                 
                mAppInstallObserver = new AppDirObserver(mAppInstallDir.getPath(), OBSERVER_EVENTS, false);
                mAppInstallObserver.startWatching();
                scanDirLI(mAppInstallDir, 0, scanMode, 0);
    
                mDrmAppInstallObserver = new AppDirObserver(mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false);
                mDrmAppInstallObserver.startWatching();
                scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,scanMode, 0);
            } else {
                mAppInstallObserver = null;
                mDrmAppInstallObserver = null;
            }
利用FileObserver来监控几个文件夹下面的apk文件,可以监听文件或者目录的添加或者删除事件并作出响应。


8.2.9 调用scanDirLI方法扫描并安装apk文件
上面的过程中出现了一个函数scanDirLI函数,它的实现比较复杂后面会进行介绍

8.2.10更新package文件
mSettings.writeLPr();方法来将mSetting中存储的package信息保存到响应的文件之中


8.3 Installer和Installd
Installer是java层的接口,Installd是init启动的守护进行,两者是client和server的关系,installer的api会转化为installd的命令,他们之间的关系:
技术分享
8.3.1 Installer
这是java层提供的Installer类,提供apk安装和卸载过程中的操作
安装的过程调用install方法:
 public int install(String name, int uid, int gid) {
        StringBuilder builder = new StringBuilder("install");
        builder.append(' ');
        builder.append(name);
        builder.append(' ');
        builder.append(uid);
        builder.append(' ');
        builder.append(gid);
        return execute(builder.toString());
    }
先将name,uid,gid按照一定的规则生成一个字符串形式:"install  name   uid   gid" 然后执行execute方法:   
 private int execute(String cmd) {
        String res = transaction(cmd);//调用transaction 方法
        try {
            return Integer.parseInt(res);
        } catch (NumberFormatException ex) {
            return -1;
        }
    }
调用了transaction方法:  
 private synchronized String transaction(String cmd) {
        if (!connect()) {//连接installd服务
            Slog.e(TAG, "connection failed");
            return "-1";
        }

        if (!writeCommand(cmd)) {//向installd发送命令       
            Slog.e(TAG, "write command failed? reconnect!");
            if (!connect() || !writeCommand(cmd)) {
                return "-1";
            }
        }
        if (LOCAL_DEBUG) {
            Slog.i(TAG, "send: '" + cmd + "'");
        }
        if (readReply()) {//读取返回的数据
            String s = new String(buf, 0, buflen);
            if (LOCAL_DEBUG) {
                Slog.i(TAG, "recv: '" + s + "'");
            }
            return s;
        } else {
            if (LOCAL_DEBUG) {
                Slog.i(TAG, "fail");
            }
            return "-1";
        }
    }
三个过程: connect 进行连接;writeCommand 发送请求  ; readReply读取响应
可以看出来client端的工作仍然是由installd来完成,client将请求发送给installd来完成,真正的工作是installd

8.3.2 Installd服务
Intalld服务是init阶段启动的服务进行,在clieng发送一个命令之后会将该命令按照下面的对应方式进行映射:
struct cmdinfo {
    const char *name;  //命令名称
    unsigned numargs; //参数个数
    int (*func)(char **arg, char reply[REPLY_MAX]);  //该命令对应的函数
};

struct cmdinfo cmds[] = {
    { "ping",                 0, do_ping },
    { "install",              3, do_install },
    { "dexopt",               3, do_dexopt },
    { "movedex",              2, do_move_dex },
    { "rmdex",                1, do_rm_dex },
    { "remove",               2, do_remove },
    { "rename",               2, do_rename },
    { "freecache",            1, do_free_cache },
    { "rmcache",              1, do_rm_cache },
    { "protect",              2, do_protect },
    { "getsize",              4, do_get_size },
    { "rmuserdata",           2, do_rm_user_data },
    { "movefiles",            0, do_movefiles },
    { "linklib",              2, do_linklib },
    { "unlinklib",            1, do_unlinklib },
    { "mkuserdata",           3, do_mk_user_data },
    { "rmuser",               1, do_rm_user },
};
这里的命令是install,对应的函数是do_install,它有调用Commands.c中的install方法:
int install(const char *pkgname, uid_t uid, gid_t gid)
{
    char pkgdir[PKG_PATH_MAX];
    char libdir[PKG_PATH_MAX];

    if ((uid < AID_SYSTEM) || (gid < AID_SYSTEM)) {
        LOGE("invalid uid/gid: %d %d\n", uid, gid);
        return -1;
    }

    if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) {
        LOGE("cannot create package path\n");
        return -1;
    }

    if (create_pkg_path(libdir, pkgname, PKG_LIB_POSTFIX, 0)) {
        LOGE("cannot create package lib path\n");
        return -1;
    }

    if (mkdir(pkgdir, 0751) < 0) {
        LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
        return -errno;
    }
    if (chmod(pkgdir, 0751) < 0) {
        LOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno));
        unlink(pkgdir);
        return -errno;
    }
    if (chown(pkgdir, uid, gid) < 0) {
        LOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno));
        unlink(pkgdir);
        return -errno;
    }
    if (mkdir(libdir, 0755) < 0) {
        LOGE("cannot create dir '%s': %s\n", libdir, strerror(errno));
        unlink(pkgdir);
        return -errno;
    }
    if (chmod(libdir, 0755) < 0) {
        LOGE("cannot chmod dir '%s': %s\n", libdir, strerror(errno));
        unlink(libdir);
        unlink(pkgdir);
        return -errno;
    }
    if (chown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) {
        LOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno));
        unlink(libdir);
        unlink(pkgdir);
        return -errno;
    }
    return 0;
}
创建各种数据目录并对目录权限进行处理,这里只是创建了目录数据而已,APK文件的安装还是很复杂的,后面会分析。







深入理解PackageManagerService

标签:

原文地址:http://blog.csdn.net/yujun411522/article/details/46469087

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!