Android PackageManager 用法


參考資料:http://blog.csdn.net/qinjuning/article/details/6867806,有改動。

PackageManger的主要職責是管理應用程序包,通過它可以獲取應用程序信息。


AnroidManifest.xml文件節點說明:



一 、相關類介紹

1. PackageItemInfo類

AndroidManifest.xml文件中所有節點的基類,並不直接使用,而是由子類繼承然后調用相應方法。

常用字段:

int icon             資源圖片在R文件中的值 (對應於android:icon屬性)
int labelRes         label在R文件中的值(對應於android:label屬性)
String name          節點的name值 (對應於android:name屬性)
String packagename   應用程序的包名 (對應於android:packagename屬性)

常用方法:
Drawable loadIcon(PackageManager pm)          獲得當前應用程序的圖標
CharSequence loadLabel(PackageManager pm)     獲得當前應用程序的label,從上圖可知是app_name

PackageItemInfo繼承關系圖:


2. ActivityInfo類

<activity>或者 <receiver>節點信息 。可獲取theme 、launchMode、launchmode等屬性

常用方法繼承自PackageItemInfo類,下同。

3. ServiceInfo類

<service>節點信息。


4. ApplicationInfo類

<application>節點的信息。

字段說明:

flags字段:
FLAG_SYSTEM 系統應用程序
FLAG_EXTERNAL_STORAGE 表示該應用安裝在sdcard中

5. ResolveInfo類

根據<intent-filter>節點獲取其上一層目錄的信息,通常是<activity>、<receiver>、<service>節點信息。
常用字段:
ActivityInfo activityInfo    獲取 ActivityInfo對象,即<activity>或<receiver>節點信息
ServiceInfo serviceInfo     獲取 ServiceInfo對象,即<service>節點信息

ApplicationInfo與ResolveInfo比較:前者能夠得到Icon、Label、meta-data、description。后者只能得到Icon、Label。


6. PackageInfo類

AndroidManifest.xml文件的信息

常用字段:

String packageName               包名
ActivityInfo[] activities        所有<activity>節點信息
ApplicationInfo applicationInfo  <application>節點信息,只有一個
ActivityInfo[] receivers         所有<receiver>節點信息,多個
ServiceInfo[] services           所有<service>節點信息 ,多個


7. PackageManger 類

通過getPackageManager()方法獲得。
常用方法:
PackageManager getPackageManager();
// 獲得一個PackageManger對象

Drawable getApplicationIcon(String packageName);
// 返回給定包名的圖標,否則返回null

ApplicationInfo getApplicationInfo(String packageName, int flags);
// 返回該ApplicationInfo對象
// flags標記通常直接賦予常數0

List<ApplicationInfo> getInstalledApplications(int flags);
// 返回給定條件的所有ApplicationInfo
// flag為一般為GET_UNINSTALLED_PACKAGES,后續可進一步過濾結果

List<PackageInfo> getInstalledPackages(int flags);
// 返回給定條件的所有PackageInfo

ResolveInfo resolveActivity(Intent intent, int flags);
// 返回給定條件的ResolveInfo對象(本質上是Activity) 
// intent 是查詢條件,Activity所配置的action和category
// 可選flags:
// MATCH_DEFAULT_ONLY :Category必須帶有CATEGORY_DEFAULT的Activity,才匹配
// GET_INTENT_FILTERS :匹配Intent條件即可
// GET_RESOLVED_FILTER :匹配Intent條件即可

List<ResolveInfo> queryIntentActivities(Intent intent, int flags);
// 返回給定條件的所有ResolveInfo對象(本質上是Activity)

ResolveInfo resolveService(Intent intent, int flags);
// 返回給定條件的ResolveInfo對象(本質上是Service) 

List<ResolveInfo> queryIntentServices(Intent intent, int flags);
// 返回給定條件的所有ResolveInfo對象(本質上是Service),集合對象
 
         
 
         

8. PackageStats 類

安裝包的大小信息。AndroidSDK中並沒有顯式提供方法獲得PackageStats對象,只能通過反射機制來調用系統中隱藏的函數(@hide)。

常用字段:

long cachesize      緩存大小
long codesize       應用程序大小
long datasize       數據大小
String packageName  包名


PS:應用程序的總大小 = cachesize  + codesize  + datasize。


二、常用代碼片

1.根據PackageInfo對象獲取APP信息:

ApplicationInfo applicationInfo = packageInfo.applicationInfo;
// APP 包名
String packageName = packageInfo.packageName;
// APP icon
Drawable icon = packageManager.getApplicationIcon(applicationInfo);
// APP 名稱
String appName = packageManager.getApplicationLabel(applicationInfo).toString();
// APP 權限
String[] permissions = packageManager.getPackageInfo(packageName,PackageManager.GET_PERMISSIONS).requestedPermissions;

2.根據ResolveInfo對象獲取APP信息:

// APP包名
resolve.activityInfo.packageName;
// APP icon
resolve.loadIcon(packageManager);
// APP名稱
resolve.loadLabel(packageManager).toString();

3.根據包名獲取APP中的主Activity:

Intent intent = new Intent(Intent.ACTION_MAIN);    
intent.setPackage(packageName);  
intent.addCategory(Intent.CATEGORY_LAUNCHER); 
List<ResolveInfo> resolveInfos = pManager.queryIntentActivities(intent, 0);
// 一個App中只有一個主Activity,直接取出。注意不是任何包中都有主Activity
String mainActivityName = "";
if (resolveInfos != null && resolveInfos.size() >= 1) {
    mainActivityName = resolveInfos.get(0).activityInfo.name;
}

4.根據包名獲取APP信息:

PackageManager pManager = context.getPackageManager();
PackageInfo packageInfo = pManager.getPackageInfo(packageName, 0);
ApplicationInfo appInfo = packageInfo.applicationInfo;

// 獲取App名
String appName = pManager.getApplicationLabel(appInfo).toString();
//// 也可以使用如下方法
//String appName = appInfo.loadLabel(pManager).toString();
// 獲取App Icon
Drawable icon = pManager.getApplicationIcon(appInfo);
//// 也可以用如下兩種方法
//Drawable icon = pManager.getApplicationIcon(packageName);
//Drawable icon = appInfo.loadIcon(pManager);
// 獲取App versionName
String versionName = packageInfo.versionName; // versionName在xml的根節點中,只能用PackageInfo獲取
// 獲取權限
PackageInfo pPermissionInfo = pManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); 
String[] permissions = pPermissionInfo.requestedPermissions;

5.批量獲取App信息的兩種方法:

PackageManager packageManager = getPackageManager();

// 法一:通過解析AndroidManifest.xml的<application>標簽中得到,可獲取所有的app。
List<ApplicationInfo> applicationList = packageManager
                  .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);

// 法二:通過Intent查找相關的Activity,更准確,但無法獲取Provider等應用
// 通過解析<Intent-filter>標簽得到
// <action android:name=”android.intent.action.MAIN”/>
// <action android:name=”android.intent.category.LAUNCHER”/>
Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> resolveList = packageManager.queryIntentActivities(intent, 0);

6.區分系統APP、第三方APP、安裝在SDCard上的APP:

/** 判斷是不是系統APP **/
// FLAG_SYSTEM = 1<<0,if set, this application is installed in the device's system image.
// 下面&運算有兩種結果:
// 1,則flags的末位為1,即系統APP
// 0,則flags的末位為0,即非系統APP
if ( (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 1 ){
    ......
}

/** 判斷是不是第三方APP **/
// FLAG_SYSTEM = 1<<0,同上
if ( (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
     ......
}
//本來是系統程序,被用戶手動更新后,該系統程序也成為第三方應用程序了
// FLAG_UPDATED_SYSTEM_APP = 1<<7, this is set if this application has been 
// install as an update to a built-in system application.
else if ((applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 1) {
     ......
}

/** 判斷是不是安裝在SDCard的應用程序 **/
// FLAG_EXTERNAL_STORAGE = 1<<18,Set to true if the application is
// currently installed on external/removable/unprotected storage
if ( (applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 1) {
    ......
} 

三、工具類

獲取APP信息:
/**
 * 獲取手機上安裝的所有APP的信息 配合AppInfo類使用
 */
public class AppInfoUtil {
    public static final int GET_ALL_APP = 0; // 所有APP
    public static final int GET_SYSTEM_APP = 1; // 系統預裝APP
    public static final int GET_THIRD_APP = 2; // 第三方APP
    public static final int GET_SDCARD_APP = 3; // SDCard的APP

    private static AppInfoUtil infoUtil;

    private PackageManager pManager;

    // 所有應用
    private List<PackageInfo> allPackageList;

    // 篩選結果
    private List<PackageInfo> result;

    /** 私有構造器 **/
    private AppInfoUtil(Context context) {
        pManager = context.getPackageManager();
        result = new ArrayList<PackageInfo>();
    }

    /** 單例 **/
    public static AppInfoUtil getInstance(Context context) {
        if (infoUtil == null) {
            infoUtil = new AppInfoUtil(context);
        }
        return infoUtil;
    }

    /** 獲取已安裝的APP **/
    public List<AppInfo> getInstalledApps(int type) {
        // 0 表示不接受任何參數。其他參數都帶有限制
        // 版本號、APP權限只能通過PackageInfo獲取,故這里不使用getInstalledApplications()方法
        allPackageList = pManager.getInstalledPackages(0);
        if (allPackageList == null) {
            Log.e("AppInfoUtil類", "getInstalledApps()方法中的allPackageList為空");
            return null;
        }
        // 根據APP名排序
        Collections.sort(allPackageList, new PackageInfoComparator(pManager));
        // 篩選
        result.clear();
        switch (type) {
        case GET_ALL_APP:
            result = allPackageList;
            break;
        case GET_SYSTEM_APP: // 系統自帶APP
            for (PackageInfo info : allPackageList) {
                // FLAG_SYSTEM = 1<<0,if set, this application is installed in
                // the device's system image.
                // 下面&運算有兩種結果:
                // 1,則flags的末位為1,即系統APP
                // 0,則flags的末位為0,即非系統APP
                if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 1) {
                    result.add(info);
                }
            }
            break;
        case GET_THIRD_APP: // 第三方APP
            for (PackageInfo info : allPackageList) {
                // FLAG_SYSTEM = 1<<0,同上
                if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                    result.add(info);
                }
                // 本來是系統程序,被用戶手動更新后,該系統程序也成為第三方應用程序了
                // FLAG_UPDATED_SYSTEM_APP = 1<<7, this is set if this
                // application has been
                // install as an update to a built-in system application.
                else if ((info.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 1) {
                    result.add(info);
                }
            }
            break;
        case GET_SDCARD_APP: // 安裝在SDCard的應用程序
            for (PackageInfo info : allPackageList) {
                // FLAG_EXTERNAL_STORAGE = 1<<18,Set to true if the application
                // is
                // currently installed on external/removable/unprotected storage
                if ((info.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 1) {
                    result.add(info);
                }
            }
            break;
        }
        return getAppInfoByPackageInfo(result);
    }

    public List<AppInfo> getAppInfoByIntent(Intent intent) {
        List<ResolveInfo> resolveInfos = pManager.queryIntentActivities(intent,
                PackageManager.GET_INTENT_FILTERS);
        // 調用系統排序 , 根據name排序
        // 此排序會將系統自帶App與用戶安裝的APP分開排序
        Collections.sort(resolveInfos, new ResolveInfo.DisplayNameComparator(
                pManager));
        // // 此排序會將系統自帶App與用戶安裝的APP混合排序
        // Collections.sort(resolveInfos, new DisplayNameComparator(pManager));
        return getAppInfobyResolveInfo(resolveInfos);
    }

    /** 獲取單個App圖標 **/
    public Drawable getAppIcon(String packageName) throws NameNotFoundException {
        Drawable icon = pManager.getApplicationIcon(packageName);
        return icon;
    }

    /** 獲取單個App名稱 **/
    public String getAppName(String packageName) throws NameNotFoundException {
        ApplicationInfo appInfo = pManager.getApplicationInfo(packageName, 0);
        String appName = pManager.getApplicationLabel(appInfo).toString();
        return appName;
    }

    /** 獲取單個App版本號 **/
    public String getAppVersion(String packageName)
            throws NameNotFoundException {
        PackageInfo packageInfo = pManager.getPackageInfo(packageName, 0);
        String appVersion = packageInfo.versionName;
        return appVersion;
    }

    /** 獲取單個App的所有權限 **/
    public String[] getAppPermission(String packageName)
            throws NameNotFoundException {
        PackageInfo packageInfo = pManager.getPackageInfo(packageName,
                PackageManager.GET_PERMISSIONS);
        String[] permission = packageInfo.requestedPermissions;
        return permission;
    }

    /** 獲取單個App的簽名 **/
    public String getAppSignature(String packageName)
            throws NameNotFoundException {
        PackageInfo packageInfo = pManager.getPackageInfo(packageName,
                PackageManager.GET_SIGNATURES);
        String allSignature = packageInfo.signatures[0].toCharsString();
        return allSignature;
    }

    // /** 使用示例 **/
    // public static void main(String[] args) {
    // AppInfoUtil appInfoUtil = AppInfo.getInstance(context);
    //
    // // 獲取所有APP
    // List<AppInfo> allAppInfo = appInfoUtil.getInstalledApps(AppInfoUtil.GET_ALL_APP);
    // for (AppInfo app : allAppInfo) {
    // String packageName = app.getPackageName();
    // String appName = app.getAppName();
    // Drawable icon = app.getIcon();
    // String versionName = app.getVersionName();
    // String[] permissions = app.getPermissions();
    // // 自由發揮...
    // }
    //
    // // 獲取單個APP的信息
    // String appName = appInfoUtil.getAppName(packageName);
    // ...
    // }

    /** 從PackageInfo的List中提取App信息 **/
    private List<AppInfo> getAppInfoByPackageInfo(List<PackageInfo> list) {
        List<AppInfo> appList = new ArrayList<AppInfo>();
        for (PackageInfo info : list) {
            // 獲取信息
            String packageName = info.applicationInfo.packageName;
            String appName = pManager.getApplicationLabel(info.applicationInfo)
                    .toString();
            Drawable icon = pManager.getApplicationIcon(info.applicationInfo);
            // // 也可以用如下方法獲取APP圖標,顯然更煩瑣
            // ApplicationInfo applicationInfo =
            // pManager.getApplicationInfo(packageName, 0);
            // Drawable icon = applicationInfo.loadIcon(pManager);
            String versionName = info.versionName;
            String[] permissions = info.requestedPermissions;
            String launchActivityName = getLaunchActivityName(packageName);
            // 儲存信息
            AppInfo appInfo = new AppInfo();
            appInfo.setPackageName(packageName);
            appInfo.setAppName(appName);
            appInfo.setIcon(icon);
            appInfo.setVersionName(versionName);
            appInfo.setPermissions(permissions);
            appInfo.setLaunchActivityName(launchActivityName);
            appList.add(appInfo);
        }
        return appList;
    }

    /** 從ResolveInfo的List中提取App信息 **/
    private List<AppInfo> getAppInfobyResolveInfo(List<ResolveInfo> list) {
        List<AppInfo> appList = new ArrayList<AppInfo>();
        for (ResolveInfo info : list) {
            String packageName = info.activityInfo.packageName;
            String appName = info.loadLabel(pManager).toString();
            Drawable icon = info.loadIcon(pManager);
            String launchActivityName = getLaunchActivityName(packageName);
            AppInfo appInfo = new AppInfo();
            appInfo.setPackageName(packageName);
            appInfo.setAppName(appName);
            appInfo.setIcon(icon);
            appInfo.setLaunchActivityName(launchActivityName);
            appList.add(appInfo);
        }
        return appList;
    }

    /** 獲取指定包中主Activity的類名,並不是所有包都有主Activity **/
    private String getLaunchActivityName(String packageName) {
        // 根據PackageInfo對象取不出其中的主Activity,須用Intent
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.setPackage(packageName);
        List<ResolveInfo> resolveInfos = pManager.queryIntentActivities(intent,
                0);
        String mainActivityName = "";
        if (resolveInfos != null && resolveInfos.size() >= 1) {
            mainActivityName = resolveInfos.get(0).activityInfo.name;
        }
        return mainActivityName;
    }

    /** 此比較器直接復制Android源碼,但是卻可以把系統APP與用戶APP混合排列,何解? **/
    private static class DisplayNameComparator implements
            Comparator<ResolveInfo> {
        public DisplayNameComparator(PackageManager pm) {
            mPM = pm;
        }

        public final int compare(ResolveInfo a, ResolveInfo b) {
            CharSequence sa = a.loadLabel(mPM);
            if (sa == null)
                sa = a.activityInfo.name;
            CharSequence sb = b.loadLabel(mPM);
            if (sb == null)
                sb = b.activityInfo.name;
            return sCollator.compare(sa.toString(), sb.toString());
        }

        private final Collator sCollator = Collator.getInstance();
        private PackageManager mPM;
    }

    /** 自定義的PackageInfo排序器 **/
    private static class PackageInfoComparator implements
            Comparator<PackageInfo> {
        public PackageInfoComparator(PackageManager pm) {
            mPM = pm;
        }

        public final int compare(PackageInfo a, PackageInfo b) {
            CharSequence sa = mPM.getApplicationLabel(a.applicationInfo);
            CharSequence sb = mPM.getApplicationLabel(b.applicationInfo);
            return sCollator.compare(sa.toString(), sb.toString());
        }

        private final Collator sCollator = Collator.getInstance();
        private PackageManager mPM;
    }

}

配套Model,AppInfo.java:
/**
 * App信息類
 */
public class AppInfo {
    // 包名
    private String packageName;
    // APP名
    private String appName;
    // 圖標
    private Drawable icon;
    // 版本號
    private String versionName;
    // 權限
    private String[] permissions;
    // 主Activity的類名
    private String launchActivityName; 
    
    public String getLaunchActivityName() {
        return launchActivityName;
    }

    public void setLaunchActivityName(String launchActivityName) {
        this.launchActivityName = launchActivityName;
    }

    public AppInfo() {}

    public String getPackageName() {
        return packageName;
    }

    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }

    public String getAppName() {
        return appName;
    }

    public void setAppName(String appName) {
        this.appName = appName;
    }

    public Drawable getIcon() {
        return icon;
    }

    public void setIcon(Drawable icon) {
        this.icon = icon;
    }

    public String getVersionName() {
        return versionName;
    }

    public void setVersionName(String versionName) {
        this.versionName = versionName;
    }

    public String[] getPermissions() {
        return permissions;
    }

    public void setPermissions(String[] permissions) {
        this.permissions = permissions;
    };
    
    
}


四、通過反射獲取APP包的大小

AndroidSDK中並沒有顯式提供方法獲得PackageStats對象,只能通過反射機制來調用系統中隱藏的函數(@hide)。

具體方法如下:
第一步、  通過反射機制調用getPackageSizeInfo() ,方法原型為:

/*
 * @param packageName 應用程序包名
 * @param observer 當查詢包的信息大小操作完成后
 * 將回調給IPackageStatsObserver類中的onGetStatsCompleted()方法,
 * 並且我們需要的PackageStats對象也封裝在其參數里.
 * @hide //隱藏函數的標記
 */
public abstract void getPackageSizeInfo(String packageName, IPackageStatsObserver observer);
{
    //
}

內部調用流程如下,這個知識點較為復雜,知道即可:
getPackageSizeInfo方法內部調用getPackageSizeInfoLI(packageName, pStats)方法來完成包狀態獲取。
getPackageSizeInfoLI方法內部調用Installer.getSizeInfo(String pkgName, String apkPath,String fwdLockApkPath, PackageStats
pStats),繼而將包狀態信息返回給參數pStats。
getSizeInfo這個方法內部是以本機Socket方式連接到Server,然后向server發送一個文本字符串命令,格式:getsize apkPath fwdLockApkPath 給server。
Server將結果返回,並解析到pStats中。
掌握這個調用知識鏈即可。


實現代碼:

/** 獲取指定包的大小信息 **/
public void queryPackageSize(String packageName) throws Exception {
    Log.i(TAG, "packageName:" + packageName);
    if (packageName != null) {
        // 使用反射機制得到PackageManager類的隱藏函數getPackageSizeInfo  
        PackageManager pManager = getPackageManager();
        //通過反射機制獲得該隱藏函數  
        Method getPackageSizeInfo = pManager.getClass().getMethod("getPackageSizeInfo"
                                    , String.class,IPackageStatsObserver.class);  
        //調用該函數,並且給其分配參數 ,待調用流程完成后會回調PkgSizeObserver類的函數  
        getPackageSizeInfo.invoke(pManager, packageName,new PkgSizeObserver());  
    }
}


第二步、由於需要獲得系統級的服務或類,我們必須加入Android系統形成的AIDL文件,共兩個: IPackageStatsObserver.aidl 和 PackageStats.aidl文件。將它們放置在android.content.pm包路徑下。 


IPackageStatsObserver.aidl 文件:

package android.content.pm;  
  
import android.content.pm.PackageStats;  
/** 
 * API for package data change related callbacks from the Package Manager. 
 * Some usage scenarios include deletion of cache directory, generate 
 * statistics related to code, data, cache usage(TODO) 
 * {@hide} 
 */  
oneway interface IPackageStatsObserver {  
      
    void onGetStatsCompleted(in PackageStats pStats, boolean succeeded);  
}  


PackageStats.aidl文件:

package android.content.pm;  
  
parcelable PackageStats; 

第三步、創建一個類繼承至IPackageStatsObserver.Stub()它本質上實現了Binder機制。當我們把該類的一個實例通過getPackageSizeInfo()調用時,該函數啟動中間流程去獲取相關包的信息大小,掃描完成后,將查詢信息回調至該類的onGetStatsCompleted(PackageStats pStats, boolean succeeded)方法,信息大小封裝在此實例上。

實現代碼:
/** aidl文件形成的Bindler機制服務類 **/
public class PkgSizeObserver extends IPackageStatsObserver.Stub{  
    /*** 回調函數, 
     * @param pStatus ,返回數據封裝在PackageStats對象中 
     * @param succeeded  代表回調成功 
     */   
    @Override  
    public void onGetStatsCompleted(PackageStats pStats, boolean succeeded) throws RemoteException {  
        cachesize = pStats.cacheSize; //緩存大小  
        datasize = pStats.dataSize;  //數據大小   
        codesize = pStats.codeSize;  //應用程序大小  
        totalsize = cachesize + datasize + codesize; 
    }  
}  


第四步、獲取pStats的屬性值(代碼見上),再轉換為對應的以kb/mb為計量單位的字符串。下面代碼中,1 << 10是二進制的左移,相當於乘以2的10次方,即乘1024

實現代碼:

/** 系統函數,字符串轉換**/
private String formateFileSize(long size){
    String str = "";
    double newSize = 0;
    if (size == 0) {
        str = "0.00 B";
    } else if (size < (1 << 10)) {
        newSize = size;
        str = newSize + " B";
    } else if (size < (1 << 20)){
        newSize = 1.0 * size / (1 << 10);
        str = String.format("%.2f", newSize) + " KB";
    } else if (size < (1 << 30)) {
        newSize = 1.0 * size / (1 << 20);
        str = String.format("%.2f", newSize) + " MB";
    }
    return str;   
}  

為了能夠通過反射獲取應用程序大小,必須加入以下權限,否則會出現警告且得不到實際值。

<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"></uses-permission> 

流程圖如下:


五、Demo


代碼見筆者的Github: https://github.com/kinglearnjava/AppInfo

版權聲明:本文為博主原創文章,未經博主允許不得轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM