ActivityManagerService 启动初探

·  阅读 752

在之前的Android SystemServer启动(二)中,分析到在SystemServer中会启动大量的Service,其中就有一个比较特殊的Service,它就是ActivityManagerService

今天我们就来了解一下ActivityManageerService的启动,下面都将其简称为AMS

启动AMS

AMS的启动发生在SystemServer启动过程中的BootPhase 0阶段。

在三大分类服务的启动服务中,即startBootstrapServices方法中

private void startBootstrapServices() {
	...

    ActivityTaskManagerService atm = mSystemServiceManager.startService(
            ActivityTaskManagerService.Lifecycle.class).getService();
    mActivityManagerService = ActivityManagerService.Lifecycle.startService(
            mSystemServiceManager, atm);
    mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
    mActivityManagerService.setInstaller(installer);
    mWindowManagerGlobalLock = atm.getGlobalLock();

    ...
}
复制代码

AMS的是通过AMS的内部类Lifecycle来创建的,同时调用Lifecycle.onStart()来启动AMS

public static final class Lifecycle extends SystemService {
    private final ActivityManagerService mService;
    private static ActivityTaskManagerService sAtm;

    public Lifecycle(Context context) {
        super(context);
        mService = new ActivityManagerService(context, sAtm);
    }

    public static ActivityManagerService startService(
            SystemServiceManager ssm, ActivityTaskManagerService atm) {
        sAtm = atm;
        return ssm.startService(ActivityManagerService.Lifecycle.class).getService();
    }

    @Override
    public void onStart() {
        mService.start();
    }
    
    ...
}
复制代码

Lifecycle继承于SystemService,通过ssmSystemServiceManager来启动这个Service

内部是通过反射来创建Lifecycle实例,而在Lifecycle实例的过程中会创建AMS

所以接下来我们来看下AMS实例化过程中做了什么

public ActivityManagerService(Context systemContext, ActivityTaskManagerService atm) {
	...

	// 创建名为ActivityManager的线程,并创建MainHandler 
	// 处理各种状态信息的线程(GC、service_timeout、update_time_zone、kill_application、kill_zygote)
    mHandlerThread = new ServiceThread(TAG,
            THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
    mHandlerThread.start();
    mHandler = new MainHandler(mHandlerThread.getLooper());
    // 获取UiHandler
    mUiHandler = mInjector.getUiHandler(this);

    // 创建与启动proc线程
    mProcStartHandlerThread = new ServiceThread(TAG + ":procStart",
            THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
    mProcStartHandlerThread.start();
    mProcStartHandler = new Handler(mProcStartHandlerThread.getLooper());

    // 定义一些常量,管理ActivityManager的行为
    mConstants = new ActivityManagerConstants(mContext, this, mHandler);
    // 追踪活跃的uid
    final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */);
    mProcessList.init(this, activeUids);
    // 低内存探测器
    mLowMemDetector = new LowMemDetector(this);
    mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);

    // 前台广播接收器
    final BroadcastConstants foreConstants = new BroadcastConstants(
            Settings.Global.BROADCAST_FG_CONSTANTS);
    foreConstants.TIMEOUT = BROADCAST_FG_TIMEOUT;

    // 后台广播接收器
    final BroadcastConstants backConstants = new BroadcastConstants(
            Settings.Global.BROADCAST_BG_CONSTANTS);
    backConstants.TIMEOUT = BROADCAST_BG_TIMEOUT;

    // 卸载广播接收器
    final BroadcastConstants offloadConstants = new BroadcastConstants(
            Settings.Global.BROADCAST_OFFLOAD_CONSTANTS);
    offloadConstants.TIMEOUT = BROADCAST_BG_TIMEOUT;

    offloadConstants.SLOW_TIME = Integer.MAX_VALUE;

    mEnableOffloadQueue = SystemProperties.getBoolean(
            "persist.device_config.activity_manager_native_boot.offload_queue_enabled", false);

    mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
            "foreground", foreConstants, false);
    mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
            "background", backConstants, true);
    mOffloadBroadcastQueue = new BroadcastQueue(this, mHandler,
            "offload", offloadConstants, true);
    mBroadcastQueues[0] = mFgBroadcastQueue;
    mBroadcastQueues[1] = mBgBroadcastQueue;
    mBroadcastQueues[2] = mOffloadBroadcastQueue;

    mServices = new ActiveServices(this);
    mProviderMap = new ProviderMap(this);
    mPackageWatchdog = PackageWatchdog.getInstance(mUiContext);
    mAppErrors = new AppErrors(mUiContext, this, mPackageWatchdog);

    final File systemDir = SystemServiceManager.ensureSystemDir();

    // 电池状态
    mBatteryStatsService = new BatteryStatsService(systemContext, systemDir,
            BackgroundThread.get().getHandler());
    mBatteryStatsService.getActiveStatistics().readLocked();
    mBatteryStatsService.scheduleWriteToDisk();
    mOnBattery = DEBUG_POWER ? true
            : mBatteryStatsService.getActiveStatistics().getIsOnBattery();
    mBatteryStatsService.getActiveStatistics().setCallback(this);
    mOomAdjProfiler.batteryPowerChanged(mOnBattery);

    // 进程状态
    mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));

    mAppOpsService = mInjector.getAppOpsService(new File(systemDir, "appops.xml"), mHandler);

    ... 

    mActivityTaskManager = atm;
    mActivityTaskManager.initialize(mIntentFirewall, mPendingIntentController,
            DisplayThread.get().getLooper());
    mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);

    // 进程cpu统计线程
    mProcessCpuThread = new Thread("CpuTracker") {
        @Override
        public void run() {
            synchronized (mProcessCpuTracker) {
                mProcessCpuInitLatch.countDown();
                mProcessCpuTracker.init();
            }
            while (true) {
                try {
                    try {
                        synchronized(this) {
                            final long now = SystemClock.uptimeMillis();
                            long nextCpuDelay = (mLastCpuTime.get()+MONITOR_CPU_MAX_TIME)-now;
                            long nextWriteDelay = (mLastWriteTime+BATTERY_STATS_TIME)-now;
                            //Slog.i(TAG, "Cpu delay=" + nextCpuDelay
                            //        + ", write delay=" + nextWriteDelay);
                            if (nextWriteDelay < nextCpuDelay) {
                                nextCpuDelay = nextWriteDelay;
                            }
                            if (nextCpuDelay > 0) {
                                mProcessCpuMutexFree.set(true);
                                this.wait(nextCpuDelay);
                            }
                        }
                    } catch (InterruptedException e) {
                    }
                    // 更新cpu的信息
                    updateCpuStatsNow();
                } catch (Exception e) {
                    Slog.e(TAG, "Unexpected exception collecting process stats", e);
                }
            }
        }
    };

    ...
}
复制代码

所以这里主要做的事是创建ActivityManagerprocCpuTracker线程;同时创建对应的广播接收器。

创建完AMS实例之后会调用start()方法来启动AMS

private void start() {
	// 移除所有进程组
    removeAllProcessGroups();
    // 开启进程cpu统计线程
    mProcessCpuThread.start();

    // 启动电池统计服务
    mBatteryStatsService.publish();
    mAppOpsService.publish(mContext);

    // 添加到本地LocalServices中
    LocalServices.addService(ActivityManagerInternal.class, new LocalService());
    mActivityTaskManager.onActivityManagerInternalAdded();
    mUgmInternal.onActivityManagerInternalAdded();
    mPendingIntentController.onActivityManagerInternalAdded();

    try {
    	// 等待进程cpu统计线程运行
        mProcessCpuInitLatch.await();
    } catch (InterruptedException e) {
        Slog.wtf(TAG, "Interrupted wait during start", e);
        Thread.currentThread().interrupt();
        throw new IllegalStateException("Interrupted wait during start");
    }
}
复制代码

这个时候AMS就已经启动完毕。

下面我们在来看下AMSSystemServer中都干了什么

startBootstrapServices

private void startBootstrapServices() {
	...

	mActivityManagerService.initPowerManagement();
	...

	mActivityManagerService.setSystemProcess();
	...
}
复制代码

initPowerManagement

首先会初始化PowerManagement

public void initPowerManagement() {
    mActivityTaskManager.onInitPowerManagement();
    mBatteryStatsService.initPowerManagement();
    mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
}
复制代码

最终会通过BatterStateService来初始化PowerManagement

setSystemProcess

接下来是setSystemProcess,注册相关的服务。

public void setSystemProcess() {
    try {
    	// 注册ActivityManagerService
        ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,
                DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
        // 注册 ProcessStatsService
        ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
        // 注册MemBinder
        ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false,
                DUMP_FLAG_PRIORITY_HIGH);
        // 注册GraphicsBinder
        ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
        // 注册DbBinder
        ServiceManager.addService("dbinfo", new DbBinder(this));
        if (MONITOR_CPU_USAGE) {
        	// 注册CpuBinder
            ServiceManager.addService("cpuinfo", new CpuBinder(this),
                    /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
        }
        // 注册PermissionController
        ServiceManager.addService("permission", new PermissionController(this));
        // 注册ProcessInfoService
        ServiceManager.addService("processinfo", new ProcessInfoService(this));

        // 加载包名为android的包,最终通过LoadedApk来加载
        ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
                "android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY);
        mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader());

        ...
}
复制代码

需要注意的是,这里将AMS注册到ServiceManager中,后续通过ActivityManager来获取注册的AMS

接下来就进入了startCoreServices,启动核心服务

startCoreServices

private void startCoreServices() {
	...
	mActivityManagerService.setUsageStatsManager(
                LocalServices.getService(UsageStatsManagerInternal.class));
	...
}
复制代码

这里做的事情很简单,设置UsageStateManager

startOtherServices

继续来到启动其它的服务模块

private void startOtherServices() {
	...
	mActivityManagerService.installSystemProviders();

	mActivityManagerService.setWindowManager(wm);

	mActivityManagerService.systemReady(...)
	...
}
复制代码

在这里主要做的是安装系统的Provider、设置WindowManagerServicesystemReady的准备工作。

installSystemProviders

public final void installSystemProviders() {
    List<ProviderInfo> providers;
    synchronized (this) {
        ProcessRecord app = mProcessList.mProcessNames.get("system", SYSTEM_UID);
        // 获取Providers
        providers = generateApplicationProvidersLocked(app);
        if (providers != null) {
            for (int i=providers.size()-1; i>=0; i--) {
                ProviderInfo pi = (ProviderInfo)providers.get(i);
                // 清除非系统的Providers
                if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
                    providers.remove(i);
                }
            }
        }
    }
    // 通过ActivityThread来安装系统的Providers
    if (providers != null) {
        mSystemThread.installSystemProviders(providers);
    }

    synchronized (this) {
        mSystemProvidersInstalled = true;
    }
    mConstants.start(mContext.getContentResolver());
    mCoreSettingsObserver = new CoreSettingsObserver(this);
    mActivityTaskManager.installSystemProviders();
    mDevelopmentSettingsObserver = new DevelopmentSettingsObserver();
    SettingsToPropertiesMapper.start(mContext.getContentResolver());
    mOomAdjuster.initSettings();
}
复制代码

这里主要做的就是安装系统的Providers

WindowManagerService

public void setWindowManager(WindowManagerService wm) {
    synchronized (this) {
        mWindowManager = wm;
        mActivityTaskManager.setWindowManager(wm);
    }
}
复制代码

设置WindowManagerService服务

systemReady

systemReady的逻辑比较多,我们拆开来说

public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
	goingCallback before

	if (goingCallback != null) goingCallback.run();

	goingCallback after
}
复制代码

在调用goingCallback.run之前

public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
	 synchronized(this) {
        // 第一次进来为false,跳过
        if (mSystemReady) {
            if (goingCallback != null) {
                goingCallback.run();
            }
            return;
        }

        ...

        mSystemReady = true;
    }

    ...

    ArrayList<ProcessRecord> procsToKill = null;
    synchronized(mPidsSelfLocked) {
        for (int i=mPidsSelfLocked.size()-1; i>=0; i--) {
            ProcessRecord proc = mPidsSelfLocked.valueAt(i);
            // 非president进程
            if (!isAllowedWhileBooting(proc.info)){
                if (procsToKill == null) {
                    procsToKill = new ArrayList<ProcessRecord>();
                }
                // 加入到procsTokill中
                procsToKill.add(proc);
            }
        }
    }

    synchronized(this) {
        if (procsToKill != null) {
        	// kill procsToKill中的进程
            for (int i=procsToKill.size()-1; i>=0; i--) {
                ProcessRecord proc = procsToKill.get(i);
                mProcessList.removeProcessLocked(proc, true, false, "system update done");
            }
        }

        mProcessesReady = true;
    }

    ...

    if (goingCallback != null) goingCallback.run();

    ...
}
复制代码

主要做的事情杀掉procsToKill中的进程,此时系统与进程都已经准备就绪

之后再调用goingCallback.run

mActivityManagerService.systemReady(() -> {
    
    // BootPhase 550
    mSystemServiceManager.startBootPhase(
            SystemService.PHASE_ACTIVITY_MANAGER_READY);

    try {
        mActivityManagerService.startObservingNativeCrashes();
    } catch (Throwable e) {
        reportWtf("observing native crashes", e);
    }

    traceBeginAndSlog("MakeConnectivityServiceReady");
    try {
        if (connectivityF != null) {
            connectivityF.systemReady();
        }
    } catch (Throwable e) {
        reportWtf("making Connectivity Service ready", e);
    }

    ...
    
    // BootPhase 600
    mSystemServiceManager.startBootPhase(
            SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);

    try {
        if (locationF != null) {
            locationF.systemRunning();
        }
    } catch (Throwable e) {
        reportWtf("Notifying Location Service running", e);
    }

    try {
        if (countryDetectorF != null) {
            countryDetectorF.systemRunning();
        }
    } catch (Throwable e) {
        reportWtf("Notifying CountryDetectorService running", e);
    }

    ...

}, BOOT_TIMINGS_TRACE_LOG);
复制代码

这个在Android SystemServer启动(二)中已经分析了,这里就不多介绍了。

主要是对一些服务进行systemReadysystemRuning操作。

最后来到systemReady的最后一个步骤

public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
	...

	if (goingCallback != null) goingCallback.run();

    final int currentUserId = mUserController.getCurrentUserId();
    if (currentUserId != UserHandle.USER_SYSTEM && !mUserController.isSystemUserStarted()) {
        throw new RuntimeException("System user not started while current user is:"
                + currentUserId);
    }
    mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
            Integer.toString(currentUserId), currentUserId);
    mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
            Integer.toString(currentUserId), currentUserId);

    // 调用所有SystemService的onStartUser方法
    mSystemServiceManager.startUser(currentUserId);

    synchronized (this) {
        // 开启Persistent app
        startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_AWARE);

        mBooting = true;
        if (UserManager.isSplitSystemUser() &&
                Settings.Secure.getInt(mContext.getContentResolver(),
                     Settings.Secure.USER_SETUP_COMPLETE, 0) != 0) {
            ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class);
            try {
                AppGlobals.getPackageManager().setComponentEnabledSetting(cName,
                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0,
                        UserHandle.USER_SYSTEM);
            } catch (RemoteException e) {
                throw e.rethrowAsRuntimeException();
            }
        }
        // 启动系统桌面Activity
        mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");

        mAtmInternal.showSystemReadyErrorDialogsIfNeeded();

        final int callingUid = Binder.getCallingUid();
        final int callingPid = Binder.getCallingPid();
        long ident = Binder.clearCallingIdentity();
        try {
        	// 发送USER_STARTED广播
            Intent intent = new Intent(Intent.ACTION_USER_STARTED);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);
            intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
            broadcastIntentLocked(null, null, intent,
                    null, null, 0, null, null, null, OP_NONE,
                    null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
                    currentUserId);

            // 发送USER_STARTING广播
            intent = new Intent(Intent.ACTION_USER_STARTING);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
            broadcastIntentLocked(null, null, intent,
                    null, new IIntentReceiver.Stub() {
                        @Override
                        public void performReceive(Intent intent, int resultCode, String data,
                                Bundle extras, boolean ordered, boolean sticky, int sendingUser)
                                throws RemoteException {
                        }
                    }, 0, null, null,
                    new String[] {INTERACT_ACROSS_USERS}, OP_NONE,
                    null, true, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
                    UserHandle.USER_ALL);
        } catch (Throwable t) {
            Slog.wtf(TAG, "Failed sending first user broadcasts", t);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
        mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
        mUserController.sendUserSwitchBroadcasts(-1, currentUserId);

        BinderInternal.nSetBinderProxyCountWatermarks(BINDER_PROXY_HIGH_WATERMARK,
                BINDER_PROXY_LOW_WATERMARK);
        BinderInternal.nSetBinderProxyCountEnabled(true);
        BinderInternal.setBinderProxyCountCallback(
                new BinderInternal.BinderProxyLimitListener() {
                    @Override
                    public void onLimitReached(int uid) {
                        Slog.wtf(TAG, "Uid " + uid + " sent too many Binders to uid "
                                + Process.myUid());
                        BinderProxy.dumpProxyDebugInfo();
                        if (uid == Process.SYSTEM_UID) {
                            Slog.i(TAG, "Skipping kill (uid is SYSTEM)");
                        } else {
                            killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
                                    "Too many Binders sent to SYSTEM");
                        }
                    }
                }, mHandler);

        // 恢复栈顶Activity
        mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
        // 发送User_SWITCH 广播
        mUserController.sendUserSwitchBroadcasts(-1, currentUserId);

        ...
    }
}
复制代码

其中startUser会回调之前的SystemServiceonStartUser方法

public void startUser(final int userHandle) {
    final int serviceLen = mServices.size();
    // 遍历mServices
    for (int i = 0; i < serviceLen; i++) {
        final SystemService service = mServices.get(i);
        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "onStartUser "
                + service.getClass().getName());
        long time = SystemClock.elapsedRealtime();
        try {
        	// 回调onStartUser
            service.onStartUser(userHandle);
        } catch (Exception ex) {
            Slog.wtf(TAG, "Failure reporting start of user " + userHandle
                    + " to service " + service.getClass().getName(), ex);
        }
    }
}
复制代码

startHomeOnAllDisplays最终会来到RootActivityContainerstartHomeOnDisplay方法

boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean allowInstrumenting,
        boolean fromHomeKey) {
    if (displayId == INVALID_DISPLAY) {
        displayId = getTopDisplayFocusedStack().mDisplayId;
    }

    Intent homeIntent = null;
    ActivityInfo aInfo = null;
    if (displayId == DEFAULT_DISPLAY) {
        homeIntent = mService.getHomeIntent();
        aInfo = resolveHomeActivity(userId, homeIntent);
    } else if (shouldPlaceSecondaryHomeOnDisplay(displayId)) {
        Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, displayId);
        aInfo = info.first;
        homeIntent = info.second;
    }
    if (aInfo == null || homeIntent == null) {
        return false;
    }

    if (!canStartHomeOnDisplay(aInfo, displayId, allowInstrumenting)) {
        return false;
    }

    // 设置home Intent 的Component信息
    homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
    homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK);

    // 设置extra
    if (fromHomeKey) {
        homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
    }

    final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId(
            aInfo.applicationInfo.uid) + ":" + displayId;

    // 启动桌面Activity
    mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason,
            displayId);
    return true;
}
复制代码

总结一下,在goingCallback.run之后,该阶段主要做的事情是:

  1. 调用所有之前添加的SystemServiceonStartUser方法
  2. 启动Persistent进程
  3. 启动系统桌面Activity
  4. 发送USER_STARTED广播
  5. 发送USER_STARTING广播
  6. 恢复栈顶Activity
  7. 发送User_SWITCH广播

以上就是AMSSystemServer中的全部过程, 也是AMS的启动过程。

整个过程主要做的事情是:

  1. 通过Lifecycle创建AMS实例,同时调用onStart()方法启动AMS
  2. 通过setSystemProcess注册AMSmeminfogfxinfodbinfocpuinfo等服务到ServiceManager
  3. 通过installSystemProviders来加载系统的providers
  4. 通过systemReadyreadyrunning各种服务,同时启动桌面应用,发送相关广播与恢复栈顶Activity

微信公众号:Android补给站,专注于Android进阶、算法与面试分析,还有不定期福利

推荐

android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件。开发人员可以使用android-startup来简化启动序列,并显式地设置初始化顺序与组件之间的依赖关系。 与此同时android-startup支持同步与异步等待,并通过有向无环图拓扑排序的方式来保证内部依赖组件的初始化顺序。

AwesomeGithub: 基于Github客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于Jetpack&DataBindingMVVM;项目中使用了ArouterRetrofitCoroutineGlideDaggerHilt等流行开源技术。

flutter_github: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。

android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。

daily_algorithm: 算法进阶,由浅入深,欢迎加入一起共勉。

分类:
Android
标签: