Android 12 客制化修改初探-Launcher/Settings/Bootanimation

部署运行你感兴趣的模型镜像

在这里插入图片描述
Android 12

    使用 Material You 打造的全新系统界面,富有表现力、活力和个性。使用重新设计的微件、AppSearch、游戏模式和新的编解码器扩展您的应用。支持隐私信息中心和大致位置等新的保护功能。使用富媒体内容插入功能、更简便的模糊处理功能、经过改进的原生调试功能等提高工作效率.


-----------------------------正文-------------------------------

平台: RK3588 + Android 12
本文用于记录一些基于RK3588 Android12 的客制化修改内容


Launcher & 导航栏

12带来的一个巨大的变化之一就是导航栏从SystemUI整合到了Launcher3QuickStep
在这里插入图片描述
众所周知, 曾经的SystemUI才是导航栏的拥有者, 把导航栏交给Launcher这意味着, 以后的Launcher, 不是你想动就能动的了.
要替换Launcher的就好好考虑清楚了.

从布局上看, 位于底部, 占满宽度:

packages/apps/Launcher3/quickstep/res/layout/taskbar.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<com.android.launcher3.taskbar.TaskbarDragLayer
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/taskbar_container"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:clipChildren="false">

    <com.android.launcher3.taskbar.TaskbarView
        android:id="@+id/taskbar_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:forceHasOverlappingRendering="false"
        android:layout_gravity="bottom"
        android:clipChildren="false" />

    <com.android.launcher3.taskbar.TaskbarScrimView
        android:id="@+id/taskbar_scrim"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:id="@+id/navbuttons_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom">

        <FrameLayout
            android:id="@+id/start_contextual_buttons"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
            android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
            android:paddingTop="@dimen/taskbar_contextual_padding_top"
            android:gravity="center_vertical"
            android:layout_gravity="start"/>

        <LinearLayout
            android:id="@+id/end_nav_buttons"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
            android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
            android:layout_marginEnd="@dimen/taskbar_contextual_button_margin"
            android:gravity="center_vertical"
            android:layout_gravity="end"/>

        <FrameLayout
            android:id="@+id/end_contextual_buttons"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
            android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
            android:paddingTop="@dimen/taskbar_contextual_padding_top"
            android:gravity="center_vertical"
            android:layout_gravity="end"/>
    </FrameLayout>

    <com.android.launcher3.taskbar.StashedHandleView
        android:id="@+id/stashed_handle"
        tools:comment1="The actual size and shape will be set as a ViewOutlineProvider at runtime"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/taskbar_stashed_handle_dark_color"
        android:clipToOutline="true"
        android:layout_gravity="bottom"/>

</com.android.launcher3.taskbar.TaskbarDragLayer>

end_nav_buttons 里包含了3个功能键, 动态增加按键控件

packages/apps/Launcher3/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java

    private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
            TaskbarNavButtonController navButtonController) {

        mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
                mNavButtonContainer, mControllers.navButtonController, R.id.back);
        mPropertyHolders.add(new StatePropertyHolder(mBackButton,
                flags -> {
                    // Show only if not disabled, and if not on the keyguard or otherwise only when
                    // the bouncer or a lockscreen app is showing above the keyguard
                    boolean showingOnKeyguard = (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
                            (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||
                            (flags & FLAG_KEYGUARD_OCCLUDED) != 0;
                    return (flags & FLAG_DISABLE_BACK) == 0
                            && ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
                }));
        boolean isRtl = Utilities.isRtl(mContext.getResources());
        mPropertyHolders.add(new StatePropertyHolder(
                mBackButton, flags -> (flags & FLAG_IME_VISIBLE) != 0, View.ROTATION,
                isRtl ? 90 : -90, 0));
        // Translate back button to be at end/start of other buttons for keyguard
        int navButtonSize = mContext.getResources().getDimensionPixelSize(
                R.dimen.taskbar_nav_buttons_size);
        mPropertyHolders.add(new StatePropertyHolder(
                mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
                        || (flags & FLAG_KEYGUARD_VISIBLE) != 0,
                VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));


        // home and recents buttons
        View homeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
                navButtonController, R.id.home);
        mPropertyHolders.add(new StatePropertyHolder(homeButton,
                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
                        (flags & FLAG_DISABLE_HOME) == 0));
        View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
                navContainer, navButtonController, R.id.recent_apps);
        mPropertyHolders.add(new StatePropertyHolder(recentsButton,
                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
                        (flags & FLAG_DISABLE_RECENTS) == 0));
    

按键的点击处理:

packages/apps/Launcher3/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java

    public void onButtonClick(@TaskbarButton int buttonType) {
        switch (buttonType) {
            case BUTTON_BACK:
                executeBack();
                break;
            case BUTTON_HOME:
                navigateHome();
                break;
            case BUTTON_RECENTS:
                navigateToOverview();
                break;
            case BUTTON_IME_SWITCH:
                showIMESwitcher();
                break;
            case BUTTON_A11Y:
                notifyA11yClick(false /* longClick */);
                break;
        }
    }

    private void navigateHome() {
        mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
    }

    private void navigateToOverview() {
        if (mScreenPinned) {
            return;
        }
        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
        mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE);
    }

    private void executeBack() {
        mSystemUiProxy.onBackPressed();
    }

packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewCommandHelper.java

    /**
     * Adds a command to be executed next, after all pending tasks are completed
     */
    @BinderThread
    public void addCommand(int type) {
        CommandInfo cmd = new CommandInfo(type);
        MAIN_EXECUTOR.execute(() -> addCommand(cmd));
    }

在Launcher3QuickStep存在以下服务:

packages/apps/Launcher3/quickstep/AndroidManifest.xml

        <service android:name="com.android.quickstep.TouchInteractionService"
             android:permission="android.permission.STATUS_BAR_SERVICE"
             android:directBootAware="true"
             android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.QUICKSTEP_SERVICE"/>
            </intent-filter>
        </service>

导航栏离不开SystemUI, 该服务由SystemUI绑定:

frameworks/base/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java

    private void internalConnectToCurrentUser() {
        disconnectFromLauncherService();

        // If user has not setup yet or already connected, do not try to connect
        if (!isEnabled()) {
            Log.v(TAG_OPS, "Cannot attempt connection, is enabled " + isEnabled());
            return;
        }
        mHandler.removeCallbacks(mConnectionRunnable);
        Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP)
                .setPackage(mRecentsComponentName.getPackageName());
        try {
            mBound = mContext.bindServiceAsUser(launcherServiceIntent,
                    mOverviewServiceConnection,
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
                    UserHandle.of(getCurrentUserId()));
        } catch (SecurityException e) {
            Log.e(TAG_OPS, "Unable to bind because of security error", e);
        }
        if (mBound) {
            // Ensure that connection has been established even if it thinks it is bound
            mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS);
        } else {
            // Retry after exponential backoff timeout
            retryConnectionWithBackoff();
        }
    }

替换Launcher
前面提过, 要替换Launcher需要考虑的比以前更多的, 首先, Launcher3QuickStep不能删除, 在设置中依然可以选择默认的Launcher,(这和现在某些国内的手机厂商不一样).
Settings > Apps > Default Apps > Home app
在这里插入图片描述
Launcher3QuickStep 默认桌面图标
去掉所有的桌面图标

packages/apps/Launcher3/res/xml/default_workspace_6x5.xml

diff --git a/packages/apps/Launcher3/res/xml/default_workspace_6x5.xml b/packages/apps/Launcher3/res/xml/default_workspace_6x5.xml
index b078cfd7f8..4300258cc4 100644
--- a/packages/apps/Launcher3/res/xml/default_workspace_6x5.xml
+++ b/packages/apps/Launcher3/res/xml/default_workspace_6x5.xml
@@ -15,10 +15,10 @@
 -->
 
 <favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3">
-
+       <!--AnsonCode remove appicons-->
     <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
     <!-- Mail Calendar Gallery Store Internet Camera -->
-    <resolve
+    <!--resolve
         launcher:container="-101"
         launcher:screen="0"
         launcher:x="0"
@@ -61,9 +61,9 @@
         <favorite
             launcher:uri="#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_BROWSER;end" />
         <favorite launcher:uri="http://www.example.com/" />
-    </resolve>
+    </resolve-->
 
-    <!-- Resolve camera intent if GoogleCamera is not available e.g. on emulator -->
+    <!-- Resolve camera intent if GoogleCamera is not available e.g. on emulator >
     <resolve
         launcher:container="-101"
         launcher:screen="5"
@@ -71,6 +71,6 @@
         launcher:y="0" >
         <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
         <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
-    </resolve>
+    </resolve-->
 
 </favorites>

Bootanimation

修改动画文件检测路径:

frameworks/base/cmds/bootanimation/BootAnimation.cpp


static const char OEM_BOOTANIMATION_FILE[] = "/cache/bootanimation.zip";
static const char PRODUCT_BOOTANIMATION_DARK_FILE[] = "/product/media/bootanimation-dark.zip";
static const char PRODUCT_BOOTANIMATION_FILE[] = "/data/bootanimation.zip";
static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip";
static const char APEX_BOOTANIMATION_FILE[] = "/apex/com.android.bootanimation/etc/bootanimation.zip";
static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip";
static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip";
static const char OEM_SHUTDOWNANIMATION_FILE[] = "/odm/media/shutdownanimation.zip";
static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip";
static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip";

一个小插曲: /data/bootanimation.zip 开机时读取不到. 开完机后, 调试过程中通过命令启动动画, 又可以正常读取, 很是怪异.
启动命令:

setprop ctl.start bootanim

/data/local/bootanimation.ts开机视频读取正常, 于是尝试把检测开机.zip动画的函数放到视频检测里:

void BootAnimation::checkVideoFile() {
    // add for boot video
    mVideoAnimation = false;
    if (access(SYSTEM_BOOTVIDEO_FILE, R_OK) == 0) {
        mVideoFile = (char*)SYSTEM_BOOTVIDEO_FILE;
    }
    if (access(DATA_BOOTVIDEO_FILE, R_OK) == 0) {
        mVideoFile = (char*)DATA_BOOTVIDEO_FILE;
    }
    //增加一行用于检测开机 ZIP 动画.
	findBootAnimationFile();
    std::string bootVideoEnable = android::base::GetProperty("persist.sys.bootvideo.enable", "false");
    std::string showTime = android::base::GetProperty("persist.sys.bootvideo.showtime", "-1");
    ALOGD("checkVideoFile()-->bootvideo.enable=%s, showtime=%s", bootVideoEnable.c_str(), showTime.c_str());
    if (mVideoFile != NULL && !strcmp(bootVideoEnable.c_str(), "true") && (atoi(showTime.c_str()) != 0)) {
        mVideoAnimation = true;
        ALOGD("mVideoAnimation = true");
    } else {
        ALOGD("bootvideo:No boot video animation,EXIT_VIDEO_NAME:%s,bootvideo.showtime:%s\n",
        bootVideoEnable.c_str(), showTime.c_str());
    }
    // add end
}

PS: 未解

  • ZIP动画文件检测需要放在checkVideoFile
  • /cache/bootanimation.zip 死活读不到.

Activity/Service 保持高优先级运行

首先可以编写一个简单的Activity

new Thread(){
	public void run(){
		while(true){
			//print....
			sleep(1);
		}
	}
}

实际测试会发现, 当Activity切到后台后, sleep的实际时间被拉长了, 线程优先级降下来了.
可以尝试修改保证Activity的线程优先级:

frameworks/base/services/core/java/com/android/server/am/ProcessStateRecord.java

    void setCurrentSchedulingGroup(int curSchedGroup) {
		//增加代码,
		//比如修改为前台APP:
		//curSchedGroup = ProcessList.SCHED_GROUP_TOP_APP;
        mCurSchedGroup = curSchedGroup;
        mApp.getWindowProcessController().setCurrentSchedulingGroup(curSchedGroup);
    }

Settings

  • 去除Usb调试开关

packages/apps/Settings/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java

    public void onActivityCreated(Bundle icicle) {
        super.onActivityCreated(icicle);
        //......
		PreferenceCategory cat = (PreferenceCategory)getPreferenceScreen().findPreference("debug_debugging_category");
		cat.removePreference(cat.findPreference("enable_adb"));
    }

单独写出来是因为刚开始写成了:

    getPreferenceScreen().findPreference("enable_adb");

正确的方式, 是先找到对应的PreferenceCategory, 在调用findPreference

  • 增加屏幕信息显示

packages/apps/Settings/res/xml/my_device_info.xml

//....
    <PreferenceCategory
        android:key="device_detail_category"
        android:selectable="false"
        android:title="@string/my_device_info_device_details_category_title">

		<Preference
            android:key="screen_info"
            android:order="19"
            android:title="屏幕信息"
            android:summary="分辨率1920x1080 DPI 240"/>

        <!-- SIM status -->
        <Preference
            android:key="sim_status"
            android:order="18"
            android:title="@string/sim_status_title"
            settings:keywords="@string/keywords_sim_status"
            android:summary="@string/summary_placeholder"
            settings:enableCopying="true"/>
//...            

packages/apps/Settings/src/com/android/settings/deviceinfo/aboutphone/MyDeviceInfoFragment.java

    private void initHeader() {
        //.....
        //增加代码
		androidx.preference.PreferenceCategory cat = (androidx.preference.PreferenceCategory)getPreferenceScreen().findPreference("device_detail_category");
		android.hardware.display.DisplayManager displayMgr = (android.hardware.display.DisplayManager)getContext().getSystemService(Context.DISPLAY_SERVICE);
		android.view.Display[] displays = displayMgr.getDisplays();
		int x = 0;
		int y = 0;
		int dpi = 0;
		if(displays != null && displays.length > 0){
			android.graphics.Point rSize = new android.graphics.Point();
            displays[0].getRealSize(rSize);
			android.util.DisplayMetrics dmOut = new android.util.DisplayMetrics();
            displays[0].getMetrics(dmOut);
			x = rSize.x;
			y = rSize.y;
			dpi = dmOut.densityDpi;
		}
		cat.findPreference("screen_info").setSummary("分辨率 " + x + "x" + y + " DPI " + dpi);
    }

其他

系统存储分区

device/rockchip/common/scripts/fstab_tools/fstab.in

# Android fstab file.
#<src>                                          <mnt_point>         <type>    <mnt_flags and options>                       <fs_mgr_flags>
# The filesystem that contains the filesystem checker binary (typically /system) cannot
# specify MF_CHECK, and must come before any filesystems that do specify MF_CHECK
${_block_prefix}system  /system   ext4 ro,barrier=1 ${_flags_vbmeta},first_stage_mount${_flags_avbpubkey}
${_block_prefix}vendor  /vendor   ext4 ro,barrier=1 ${_flags},first_stage_mount
${_block_prefix}odm     /odm      ext4 ro,barrier=1 ${_flags},first_stage_mount

/dev/block/by-name/boot     /boot       emmc defaults     ${_flags_chained}first_stage_mount
/dev/block/by-name/cache    /cache      ext4 noatime,nodiratime,nosuid,nodev,noauto_da_alloc,discard    wait,check
/dev/block/by-name/metadata /metadata   ext4 nodev,noatime,nosuid,discard,sync                          wait,formattable,first_stage_mount,check
/dev/block/by-name/misc     /misc       emmc defaults     defaults

/devices/platform/*usb*   auto vfat defaults      voldmanaged=usb:auto

# For sata
/devices/platform/*.sata* auto vfat defaults voldmanaged=sata:auto

# For pcie ssd
/devices/platform/*.pcie* auto vfat defaults voldmanaged=pcie:auto

/dev/block/zram0                                none                swap      defaults                                              zramsize=50%
# For sdmmc
/devices/platform/${_sdmmc_device}/mmc_host*        auto  auto    defaults        voldmanaged=sdcard1:auto
#  Full disk encryption has less effect on rk3326, so default to enable this.
/dev/block/by-name/userdata /data f2fs noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065 latemount,wait,check,fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized,keydirectory=/metadata/vold/metadata_encryption,quota,formattable,reservedsize=128M,checkpoint=fs
# for ext4
#/dev/block/by-name/userdata    /data      ext4    discard,noatime,nosuid,nodev,noauto_da_alloc,data=ordered,user_xattr,barrier=1    latemount,wait,formattable,check,fileencryption=software,quota,reservedsize=128M,checkpoint=block

默认关闭USB调试
默认persist.sys.usb.config的属性值去掉adb
如果打开了ro.debuggable, 以下代码会重新加回去.

system/core/init/property_service.cpp

static void update_sys_usb_config() {
    bool is_debuggable = android::base::GetBoolProperty("ro.debuggable", false);
    std::string config = android::base::GetProperty("persist.sys.usb.config", "");
    // b/150130503, add (config == "none") condition here to prevent appending
    // ",adb" if "none" is explicitly defined in default prop.
    if (config.empty() || config == "none") {
        InitPropertySet("persist.sys.usb.config", is_debuggable ? "adb" : "none");
    } else if (is_debuggable && config.find("adb") == std::string::npos &&
               config.length() + 4 < PROP_VALUE_MAX) {
        config.append(",adb");
        InitPropertySet("persist.sys.usb.config", config);
    }
}

参考

  1. Android进程管理1—进程优先级adj

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

<think>好的,我现在需要解决用户在使用`cp`命令时遇到的错误:“cp: target 'AAB/137-launcher-release.aab' is not a directory”。用户执行的命令是: ``` cp /d/build/CMAndroid/launcher/release/launcher-release.aab /d/JenkinsProject/workspace/Android 打包 Release AAB/137-launcher-release.aab ``` 首先,我需要分析错误信息。错误提示目标不是一个目录,这意味着`cp`命令的第二个参数被当作目录,但实际上它可能是一个文件路径,或者路径中存在空格导致解析错误。 用户的目标路径是:`/d/JenkinsProject/workspace/Android 打包 Release AAB/137-launcher-release.aab`。注意到路径中包含空格,尤其是“Android 打包 Release AAB”这部分。在Linux或Unix-like系统中,空格在命令行中会被视为参数分隔符,因此如果没有正确处理空格,命令会被拆分成多个部分,导致路径解析错误。 用户原来的命令中,目标路径被拆分成了两个部分:`/d/JenkinsProject/workspace/Android`和`打包 Release AAB/137-launcher-release.aab`。因此,`cp`命令会认为用户想把源文件复到`/d/JenkinsProject/workspace/Android`这个目录下,并且有一个名为`打包`的目录,里面还有其他路径,但实际上目标路径应该是一个完整的路径,包含空格的部分需要用引号或转义符来处理。 解决方案应该是将包含空格的路径部分用双引号括起来,或者使用反斜杠转义空格。例如: ``` cp /d/build/CMAndroid/launcher/release/launcher-release.aab "/d/JenkinsProject/workspace/Android 打包 Release AAB/137-launcher-release.aab" ``` 或者 ``` cp /d/build/CMAndroid/launcher/release/launcher-release.aab /d/JenkinsProject/workspace/Android\ 打包\ Release\ AAB/137-launcher-release.aab ``` 此外,还需要确认目标路径中的目录是否存在。如果`/d/JenkinsProject/workspace/Android 打包 Release AAB/`这个目录不存在,`cp`命令会尝试将文件复到一个不存在的目录,导致错误。因此,用户需要确保该目录已经存在,或者在复时使用`-r`选项自动创建目录结构,但注意`cp`命令的`-r`选项通常用于递归复目录,而不是创建目标目录。更合适的方式是使用`mkdir -p`先创建目录,再执行复。 另一个需要注意的点是,用户可能希望将源文件复到目标路径并重命名为`137-launcher-release.aab`。如果目标路径的目录存在,且用户有写入权限,那么正确的命令应该能够执行。如果用户没有权限,可能需要使用`sudo`,但通常在这种情况下,用户可能是在自己的项目目录下操作,所以权限问题可能不是主要原因。 总结错误的原因是路径中的空格未被正确转义,导致`cp`命令将路径拆分为多个部分,从而无法识别正确的目标路径。解决方案是正确转义空格,并确保目标目录存在。</think>你遇到的 `cp: target 'AAB/137-launcher-release.aab' is not a directory` 错误是由于 **路径中包含空格且未正确转义** 导致的。以下是具体分析和解决方案: --- ### **错误原因** 你的命令是: ```bash cp /d/build/CMAndroid/launcher/release/launcher-release.aab /d/JenkinsProject/workspace/Android 打包 Release AAB/137-launcher-release.aab ``` - **问题点**:路径 `Android 打包 Release AAB` 包含空格,而 `cp` 命令会将空格视为参数分隔符,导致路径被错误拆分为: - `/d/JenkinsProject/workspace/Android`(被识别为第一个参数) - `打包`(被识别为第二个参数) - `Release`(被识别为第三个参数) - `AAB/137-launcher-release.aab`(被识别为第四个参数) - 最终 `cp` 认为你尝试将文件复到多个目标位置,而 `AAB/137-launcher-release.aab` 不是一个目录,因此报错。 --- ### **解决方案** #### 1. **转义空格** 在路径中用 **引号包裹含空格的目录** 或 **用反斜杠 `\` 转义空格**: ```bash # 方法1:用双引号包裹完整路径 cp /d/build/CMAndroid/launcher/release/launcher-release.aab "/d/JenkinsProject/workspace/Android 打包 Release AAB/137-launcher-release.aab" # 方法2:用反斜杠转义空格 cp /d/build/CMAndroid/launcher/release/launcher-release.aab /d/JenkinsProject/workspace/Android\ 打包\ Release\ AAB/137-launcher-release.aab ``` #### 2. **检查目标目录是否存在** 如果目标目录 `/d/JenkinsProject/workspace/Android 打包 Release AAB` 不存在,需要先创建它: ```bash mkdir -p "/d/JenkinsProject/workspace/Android 打包 Release AAB" cp /d/build/CMAndroid/launcher/release/launcher-release.aab "/d/JenkinsProject/workspace/Android 打包 Release AAB/137-launcher-release.aab" ``` #### 3. **简路径名(可选)** 如果路径可控,建议将 `Android 打包 Release AAB` 改为无空格的名称(例如 `Android_Package_Release_AAB`),避免后续操作中频繁转义。 --- ### **总结** - **关键点**:路径中的空格必须通过 **引号** 或 **反斜杠** 转义,否则命令会被错误解析。 - **验证命令**:执行前可先用 `ls` 检查路径是否存在: ```bash ls -l "/d/JenkinsProject/workspace/Android 打包 Release AAB" ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值