IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android package管理之应用程序安装详解 -> 正文阅读

[移动开发]Android package管理之应用程序安装详解

系统中的应用程序分两部分,系统预装和后期安装,系统预装的apk主要是编译时,预置在以下几个目录
/system/app、/system/priv-app、/vendor/app、/vendor/priv-app、后期安装的通过以下几种途径:

APK安装方式
方法核心文件及函数解释
adb pushPMS、AppDirObserver、ADD_EVENTS将apk 文件直接推送到/data 目录下,系统监听并安装
adb installcommandline.cpp/adb_commandline->install_app:exec:cmd package//cmd.cpp onShellCommand ->PMS->PackageManagerShellCommand通过adb 调用cmd 命令,执行cmd package,最终通过PackageManagerShellCommand 调用runinstall 函数执行安装
pm installpm、cmd package在串口或者adb shell 进入终端后,通过pm 命令安装,cmd package后续步骤与adb install 一致
应用市场安装Android 4.4:PackageManager->installPackage Android 9.0 通过PackageManager->getPackageInstaller->createSession/session.commitAndroid 4.4 可以直接调用PackageManager installPackage (反射的方式)安装apk Android 9.0已经没有installPackage 接口,通过获取PackageInstaller 安装apk,下面可以给出实例代码
其他第三方安装PackageInstall APP网络上下载的apk,或者U盘安装等,通过PackageInstall APP 统一安装

通过命令行安装adb push 、adb install ,pm install ,cmd package 等,这里就不给出实例了,很简单

第三方安装code实例
public static boolean installNormal(Context context, String filePath) {
    	Intent i = new Intent(Intent.ACTION_VIEW);
        File file = new File(filePath);
        if (file == null || !file.exists() || !file.isFile() || file.length() <= 0) {
            return false;
        }
        file.setReadable(true, false);
        i.setDataAndType(Uri.parse("file://" + filePath), "application/vnd.android.package-archive");
        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(i);
        
        return true;
    }

packages/apps/PackageInstaller/AndroidManifest.xml

 <activity android:name=".InstallStart"
                android:exported="true"
                android:excludeFromRecents="true">
            <intent-filter android:priority="1">
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:scheme="content" />
                <data android:mimeType="application/vnd.android.package-archive" />
            </intent-filter>
            <intent-filter android:priority="1">
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:scheme="package" />
                <data android:scheme="content" />
            </intent-filter>
            <intent-filter android:priority="1">
                <action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
应用市场安装code实例

应用市场(厂商自己定制的)安装一般是没有界面的,直接调用底层的packageinstall 或者pm install
实例1:

StringBuilder command = new StringBuilder().append("LD_LIBRARY_PATH=/vendor/lib:/system/lib pm install ")
                .append(pmParams == null ? "" : pmParams).append(" ").append(filePath.replace(" ", "\\ "));
        CommandResult commandResult = ShellUtils.execCommand(command.toString(), !isSystemApplication(context), true);
        if (commandResult.successMsg != null
                && (commandResult.successMsg.contains("Success") || commandResult.successMsg.contains("success"))) {
        	return INSTALL_SUCCEEDED;
        }

实例2:
PackageInstallerSession.commit ==> commitLocked(); ==> PMS.installStage()

private void install(PackageManager pm, File targetFile) {
	    final long length = targetFile.length();
	    SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
	    params.setSize(length);
	
	    android.content.pm.PackageInstaller installer = pm.getPackageInstaller();
	    try {
	        final int sessionID = installer.createSession(params);
	        if (sessionID > 0) {
	            registerInstallReceiver();
	            Session session = installer.openSession(sessionID);
	            copyFile(session, targetFile, length);
	
	            Intent intent = new Intent(ACTION_INSTALLED);
	            PendingIntent pi = PendingIntent.getBroadcast(FunApplication.getInstance(), 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
	            session.commit(pi.getIntentSender());
	
	            waitForInstall();
	            session.close();
	        }
	    } catch (Exception e) {
	        unregisterInstallReceiver();
	        Log.e("SDKManagerPackageInstaller", "Install failed - " + targetFile, e);
	    }
    }

实例3(Android 4.4):

public int install(Uri src, PackageManager pm, String installFrom) {
        mRet = 0;
        int flag = 0x00000002;  // PackageManager.INSTALL_REPLACE_EXISTING
        try {
         Method method = pm.getClass().getMethod("installPackage",
                          Uri.class, IPackageInstallObserver.class,
                          int.class, String.class);
          Log.d("SDKManagerPackageInstaller", "start install");
          method.invoke(pm, src, this, flag, installFrom);
       } catch(Exception e) {
               e.printStackTrace();
       }
       waitForInstall();
        return mRet;
    }

以上,不管是什么方式,最终都会调用到PMS,通过PMS->installStage/installPackageAsUser执行后续的安装流程

PMS安装apk流程

1、installStage
PMS.installStage()会调用sendMessage将”INIT_COPY”发送给PackageHandler:

final PackageHandler mHandler;
void installStage(String packageName, File stagedDir,
            IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
            String installerPackageName, int installerUid, UserHandle user,
            PackageParser.SigningDetails signingDetails) {
       	//方便查看,简化了很多代码
        final VerificationInfo verificationInfo = new VerificationInfo();
        final OriginInfo origin = OriginInfo.fromStagedFile(stagedDir);
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        final int installReason = fixUpInstallReason();
        final InstallParams params = new InstallParams();
        params.setTraceMethod();
        msg.obj = params;
        mHandler.sendMessage(msg);
    }

PMS.installPackageAsUser 与installStage 类似:

public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
        int installFlags, String installerPackageName, int userId) {
    ...
    final Message msg = mHandler.obtainMessage(INIT_COPY);
    final VerificationInfo verificationInfo = new VerificationInfo(
            null /*originatingUri*/, null /*referrer*/, -1 /*originatingUid*/, callingUid);
    final InstallParams params = new InstallParams(origin, null /*moveInfo*/, observer,
            installFlags, installerPackageName, null /*volumeUuid*/, verificationInfo, user,
            null /*packageAbiOverride*/, null /*grantedPermissions*/,
            null /*certificates*/);
    params.setTraceMethod("installAsUser").setTraceCookie(System.identityHashCode(params));
    msg.obj = params;
    mHandler.sendMessage(msg);
    ....
}

PackageHandler用于处理apk的安装请求等消息

2、INIT_COPY
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java 内部类 PackageHandler

class PackageHandler extends Handler {
		...
        void doHandleMessage(Message msg) {
            switch (msg.what) {
                case INIT_COPY: {
                    HandlerParams params = (HandlerParams) msg.obj;
                    int idx = mPendingInstalls.size();
                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
                    if (!mBound) {
                        if (!connectToService()) {
                            Slog.e(TAG, "Failed to bind to media container service");
                            params.serviceError();
                            if (params.traceMethod != null) {
                               params.traceCookie);
                            }
                            return;
                        } else {
                            mPendingInstalls.add(idx, params);
                        }
                    } else {
                        mPendingInstalls.add(idx, params);
                        if (idx == 0) {
                            mHandler.sendEmptyMessage(MCS_BOUND);
                        }
                    }
                    break;
                }
                case MCS_BOUND: {
                    //这里后续分析
                    break;
                }
               
            }
        }
    }

INIT_COPY主要是将新的请求加入到mPendingIntalls中,等待MCS_BOUND阶段处理。加入到mPendingIntalls 中的是一个InstallParams 对象(在installStage 中构造)

3、MCS_BOUND
不管是哪种情况,INIT_COPY之后,一定会再发一个MSC_BOUND的消息
这里开始调用InstallParams类的startCopy()方法

void doHandleMessage(Message msg) {
    .......
    case MCS_BOUND: {
        ........
        if (msg.obj != null) {
            mContainerService = (IMediaContainerService) msg.obj;
            .......
        }
        if (mContainerService == null) {
            if (!mBound) {
                ............            
            } else {
                Slog.w(TAG, "Waiting to connect to media container service");
            }
        // 请求队列mPendingInstalls不为空
        } else if (mPendingInstalls.size() > 0) {
            HandlerParams params = mPendingInstalls.get(0);
            if (params != null) {
                ........
                //调用参数的startCopy函数处理安装请求
                if (params.startCopy()) {
                    ........
                    // Delete pending install
                    if (mPendingInstalls.size() > 0) {
                        mPendingInstalls.remove(0);
                    }
                    if (mPendingInstalls.size() == 0) {
                        if (mBound) {
                            ..........
                            removeMessages(MCS_UNBIND);
                            Message ubmsg = obtainMessage(MCS_UNBIND);
                            sendMessageDelayed(ubmsg, 10000);
                        }
                    } else {
                        ......
                        mHandler.sendEmptyMessage(MCS_BOUND);
                    }
                }
                .........
            }
        } else {
            // Should never happen ideally.
            Slog.w(TAG, "Empty queue");
        }
        break;
    }
.......
}

如果mPendingInstalls不为空,调用InstallParams.startCopy函数处理安装请求。
接着如果mPendingInstalls不为空,发送MCS_BOUND继续处理下一个,直到队列为空。
如果队列为空,则等待一段时间后,发送MCS_UNBIND消息断开与安装服务的绑定。

这里总结一下PMS已经完成的工作:
1.构造一个InstallParams对象(installStage /installPackageAsUser),这个对象里包含了apk文件的信息和一些安装相关的参数

2.发送INIT_COPY消息,这时如果没有连接DefaultContainerService, 先连接这个service;还有就是将apk安装请求放入mPendingInstalls里

3.再发送MCS_BOUND消息,调用InstallParams对象的startCopy()方法开始安装app;如果有多个app等待安装,循环发MSC_BOUND消息,执行安装操作

4、startCopy
InstallParams继承HandlerParams,会首先调用HandlerParams.startCopy:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

private abstract class HandlerParams {
       	...
        final boolean startCopy() {
            boolean res;
            try {
                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);

                if (++mRetries > MAX_RETRIES) {
                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                    mHandler.sendEmptyMessage(MCS_GIVE_UP);
                    handleServiceError();
                    return false;
                } else {
                    handleStartCopy();
                    res = true;
                }
            } catch (RemoteException e) {
            }
            handleReturnCode();//注意这里
            return res;
        }
        ...
    }

5、handleStartCopy
handleStartCopy函数在HandleParams抽象类定义,在其子类InstallParams来实现,我们看看与实际安装相关的handleStartCopy函数:

public void handleStartCopy() throws RemoteException {
    int ret = PackageManager.INSTALL_SUCCEEDED;
    
    // 决定是安装在手机内还是sdcard中,设置对应标志位
    if (origin.staged) {
        if (origin.file != null) {
            installFlags |= PackageManager.INSTALL_INTERNAL;
            installFlags &= ~PackageManager.INSTALL_EXTERNAL;
        } else if (origin.cid != null) {
            installFlags |= PackageManager.INSTALL_EXTERNAL;
            installFlags &= ~PackageManager.INSTALL_INTERNAL;
        } else {
            throw new IllegalStateException("Invalid stage location");
        }
    }
    ...
    // 检查APK的安装位置是否正确
    if (onInt && onSd) {
        // Check if both bits are set.
        Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
        ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
    } else if (onSd && ephemeral) {
        Slog.w(TAG,  "Conflicting flags specified for installing ephemeral on external");
        ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
    } else {
        ...
    }
    ...
    // createInstallArgs用于创建一个安装参数对象
    final InstallArgs args = createInstallArgs(this);
    
    if (ret == PackageManager.INSTALL_SUCCEEDED) {
        ...
            // 调用InstallArgs的copyApk函数
            ret = args.copyApk(mContainerService, true);
        }
    }
    mRet = ret;
}

InstallParams$handleStartCopy()主要功能是获取安装位置信息以及复制apk到指定位置。
copyApk()方法完成将需要安装的apk文件拷贝到要安装的分区的一个零时文件夹里,并重命名为base.apk,比如/data/app/vmdl1309479077.tmp/base.apk下。同时还会将.apk包里的.so库拷贝到/data/app/vmdl1309479077.tmp/libs下

6、handleReturnCode
handleReturnCode里会完成app安装的剩余动作,并发送安装成功的广播
handleReturnCode
7、installPackageLI
上图第4步,installPackageLI中完成app 安装的核心工作,后面再单独介绍一下
8、POST_INSTALL
1.发送ACTION_PACKAGE_ADDED广播
2.如果在调用installPackageAsUser()方法时有传入IPackageInstallObserver2作为回调,则调用回调方法报告安装状态
至此一个APK 安装流程完毕



关于上面提到的installPackageLI()是app 安装的的核心函数,下面再重点讲解一下

private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
    
    // PackageParser对象
    PackageParser pp = new PackageParser();
    pp.setSeparateProcesses(mSeparateProcesses);
    pp.setDisplayMetrics(mMetrics);
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
    final PackageParser.Package pkg;
    try {
        if (DEBUG_INSTALL) Slog.i(TAG, "Start parsing apk: " + installerPackageName);
        // 1.开始解析我们的package
        pkg = pp.parsePackage(tmpPackageFile, parseFlags);
        if (DEBUG_INSTALL) Slog.i(TAG, "Parsing done for apk: " + installerPackageName);
    } catch (PackageParserException e) {
        res.setError("Failed parse during installPackageLI", e);
        return;
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
    ...
    //2. 加载证书,获取签名信息
    try {
        // either use what we've been given or parse directly from the APK
        if (args.certificates != null) {
            try {
                PackageParser.populateCertificates(pkg, args.certificates);
            } catch (PackageParserException e) {
                PackageParser.collectCertificates(pkg, parseFlags);
            }
        } else {
            PackageParser.collectCertificates(pkg, parseFlags);
        }
    } catch (PackageParserException e) {
        res.setError("Failed collect during installPackageLI", e);
        return;
    }
    ...
    synchronized (mPackages) {
        // 3.检测packages是否存在
        if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
                ...
                replace = true;
                
            } else if (mPackages.containsKey(pkgName)) {
                ...
                replace = true;
                if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName);
            }
            ...           
        }
    }    
    ...
    try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
            "installPackageLI")) {
        if (replace) {
            // 4.更新已经存在的packages
            replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
                    installerPackageName, res);
        } else {
            // 5.安装新的packages
            installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
                    args.user, installerPackageName, volumeUuid, res);
        }
    }
...
}

PackageParser$parsePackage,主要是解析APK的AndroidManifest.xml,将每个标签对应的信息添加到Package的相关列表中,如将下的信息添加到Package的activities列表等。
加载apk证书,获取签名信息
检查目前安装的APK是否在系统中已存在:
已存在,则调用replacePackageLIF进行替换安装。
不存在,否则调用installNewPackageLIF进行安装。
install
第2步parsePackage()通过解析apk包和apk包里的AndroidManifest.xml,解析出所有apk的信息,放到Package对象中。

第3步collectCertificates()方法对apk文件进行签收校验,确保apk文件没有被更改过以及签名正确。第4步通过collectManifestDigest()方法获取AndroidManifest.xml的摘要值。

第5步derivePackageAbi()方法通过apk包里使用的so库确定app进程的abi,关于abi,可以查看之前的一篇文章: Android系统设置app进程abi

第6步performDexOpt()通过socket和installd通信,将apk包里的classes.dex转化为虚拟机可以直接运行的文件格式,关于dex优化,可以查看之前的一篇文章:Android编译时odex优化

第7步doRename()方法将前面handleStartCopy()里的零时文件夹/data/app/vmdl1309479077更改成/data/app/$(package_name),这个新的文件夹将是app安装后的最终目录

第8步installNewPackageLI()将完成一个新的app的安装,如果是安装之前已经存在的apk, 则这里调用的是replacePackageLI()


replacePackageLIF
如果需要替换的是系统APP,则调用Settings$disableSystemPackageLPw来disable旧的APK;如果替换的是非系统APP,则调用deletePackageLI删除旧的APK。

replacePackageLIF
    replaceSystemPackageLIF  // 系统 pkg
        removePackageLI
        disableSystemPackageLPw
        clearAppDataLIF
        scanPackageTracedLI  //安装apk
            scanPackageLI
                scanPackageDirtyLI  
        updateSettingsLI
        updatePermissionsLPw
        mSettings.writeLPr();
    replaceNonSystemPackageLIF  // 非系统 pkg
        deletePackageLIF
        clearAppDataLIF
        clearAppProfilesLIF
        scanPackageTracedLI    // 安装apk
            scanPackageLI
                scanPackageDirtyLI  
        updateSettingsLI
        updatePermissionsLPw
        mSettings.writeLPr();

不管是更新系统还是非系统apk,都会先清除之前的packages信息,然后通过scanPackageTracedLI去安装apk,安装完后更新permissions和setting,最后通过writeLPr更新packages.xml


installNewPackageLIF

private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
            int scanFlags, UserHandle user, String installerPackageName, String volumeUuid,
            PackageInstalledInfo res) {
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installNewPackage");
        // Remember this for later, in case we need to rollback this install
        String pkgName = pkg.packageName;
        if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
        // package已经存在
        synchronized(mPackages) {
            if (mSettings.mRenamedPackages.containsKey(pkgName)) {
                // A package with the same name is already installed, though
                // it has been renamed to an older name.  The package we
                // are trying to install should be installed as an update to
                // the existing one, but that has not been requested, so bail.
                res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName
                        + " without first uninstalling package running as "
                        + mSettings.mRenamedPackages.get(pkgName));
                return;
            }
            if (mPackages.containsKey(pkgName)) {
                // Don't allow installation over an existing package with the same name.
                res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName
                        + " without first uninstalling.");
                return;
            }
        }
        try {
            // 1. 安装apk
            PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags,
                    System.currentTimeMillis(), user);
            // 2. 更新setting
            updateSettingsLI(newPackage, installerPackageName, null, res, user);
            if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                prepareAppDataAfterInstallLIF(newPackage);
            } else {
                // Remove package from internal structures, but keep around any
                // data that might have already existed
                deletePackageLIF(pkgName, UserHandle.ALL, false, null,
                        PackageManager.DELETE_KEEP_DATA, res.removedInfo, true, null);
            }
        } catch (PackageManagerException e) {
            res.setError("Package couldn't be installed in " + pkg.codePath, e);
        }
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }

installNewPackageLIF会调用scanPackageTracedLI去安装apk,最终会调用scanPackageLI->scanPackageDirtyLI实际去安装apk。

installNewPackageLI
verifySignaturesLP()使用上一步中collectCertificates()里获取的签名信息进行验证,主要分两步:一是如果是安装一个系统中已经存在的同名app, 则和已经存在的app签名进行对比,保证两者签名相同;二是如果系统中已经存在和正在安装的app有相同UID的app
则需要保证安装app的签名和已经存在的使用相同UID的app签名相同。

createDataDirsLI()方法创建app的数据目录,例如: /data/data/${package_name}/

adjustCpuAbisForSharedUserLPw()方法保证如果新安装的app使用了”android:sharedUID”, 它会将它的abi调整成和系统中使用相同uid的其它app一样的abi

接下来就会在mSettings中写入正在安装app的信息,比如安装路径,数据目录路径,签名信息,abi信息等。将这些信息在/data/system/packages.xml中。同时还会将AndroidManifest.xml中的四大组件以及权限等信息,存到PackageManagerService的对应数组里,这样以后通过startActivity之类的方法打开相应的组件时,就可以找到相应的组件是否存在。需要注意的是,这些四大组件以及权限信息,只是保存在内存中,每次系统开机时,都会重新扫描apk文件,然后将它们再次存到内存中使用。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-15 18:24:43  更:2021-12-15 18:26:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:48:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码