当前位置: 首页 > 知识库问答 >
问题:

如何检查我们可以访问哪些存储卷,哪些不可以?

督飞鸣
2023-03-14

谷歌(遗憾地)计划破坏存储权限,使应用程序无法使用标准文件API(和文件路径)访问文件系统。许多人反对它,因为它改变了应用程序访问存储的方式,在很多方面,它是一个受限的API。

因此,如果我们希望处理各种存储卷并访问其中的所有文件,我们将需要在未来的Android版本上完全使用SAF(存储访问框架)(在Android Q上,我们至少可以暂时使用一个标志来使用正常的存储权限)。

例如,假设您想创建一个文件管理器,并显示设备的所有存储卷,以显示用户可以授予访问权限的内容,如果您已经拥有对每个存储卷的访问权限,您只需输入它。这样的事情似乎非常合法,但我找不到方法来做到这一点。

从API 24(这里)开始,我们终于能够列出所有的存储卷,如:

    val storageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
    val storageVolumes = storageManager.storageVolumes

而且,我们有史以来第一次有意向请求访问storageVolume(此处)。例如,如果我们想请求用户授予对主服务器的访问权(实际上,这将从那里开始,而不是真正要求任何东西),我们可以使用以下方法

startActivityForResult(storageManager.primaryStorageVolume.createOpenDocumentTreeIntent(), REQUEST_CODE__DIRECTORTY_PERMISSION)

而不是starActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE),REQUEST_CODE__DIRECTORTY_PERMISSION),并希望用户在那里选择正确的东西。

为了最终获得用户选择的访问权限,我们有这个:

@TargetApi(Build.VERSION_CODES.KITKAT)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_CODE__DIRECTORTY_PERMISSION && resultCode == Activity.RESULT_OK && data != null) {
        val treeUri = data.data ?: return
        contentResolver.takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
        val pickedDir = DocumentFile.fromTreeUri(this, treeUri)
        ...

到目前为止,我们可以申请各种存储卷的权限。。。

然而,如果你想知道哪些得到了许可,哪些没有,问题就会出现。

>

StorageVolume类的唯一ID是uuid,但它甚至不能保证返回任何内容。事实上,它在各种情况下都返回null。例如,主存储器的情况。

当使用createOpenDocumentTreeContent函数时,我注意到里面隐藏着一个Uri,可能告诉我们从哪个开始。它在extras中,在一个名为“android.provider.extra.INITIAL_URI”的键中。例如,在主存储器上检查其值时,我得到了以下结果:

content://com.android.externalstorage.documents/root/primary

当我看到我在onActivityResult中得到的Uri时,我得到的东西有点类似于#2,但是对于我所显示的treeUri变量不同:

content://com.android.externalstorage.documents/tree/primary:

为了获得到目前为止您可以访问的列表,您可以使用以下命令:

Val persistedUri权限=ContentResolver.persisted用户权限

这将返回一个Uri权限列表,每个权限都有一个Uri。可悲的是,当我使用它时,我得到了与#3上相同的结果,这与我从StorageVolume获得的结果无法相比:

content://com.android.externalstorage.documents/tree/primary%3A

如您所见,我找不到存储卷列表和用户授予的内容之间的任何映射。

我甚至不知道用户是否选择了存储卷,因为createOpenDocumentTreeIntent的功能只将用户发送到StorageVolume,但仍然可以选择一个文件夹。

我唯一拥有的是我在其他问题上找到的一大块变通函数,我认为它们不可靠,尤其是现在我们无法真正访问文件API和文件路径。

我写在这里,以防你觉得有用:

@TargetApi(VERSION_CODES.LOLLIPOP)
private static String getVolumeIdFromTreeUri(final Uri treeUri) {
    final String docId = DocumentsContract.getTreeDocumentId(treeUri);
    final int end = docId.indexOf(':');
    String result = end == -1 ? null : docId.substring(0, end);
    return result;
}

private static String getDocumentPathFromTreeUri(final Uri treeUri) {
    final String docId = DocumentsContract.getTreeDocumentId(treeUri);
    //TODO avoid using spliting of a string (because it uses extra strings creation)
    final String[] split = docId.split(":");
    if ((split.length >= 2) && (split[1] != null))
        return split[1];
    else
        return File.separator;
}

public static String getFullPathOfDocumentFile(Context context, DocumentFile documentFile) {
    String volumePath = getVolumePath(context, getVolumeIdFromTreeUri(documentFile.getUri()));
    if (volumePath == null)
        return null;
    DocumentFile parent = documentFile.getParentFile();
    if (parent == null)
        return volumePath;
    final LinkedList<String> fileHierarchy = new LinkedList<>();
    while (true) {
        fileHierarchy.add(0, documentFile.getName());
        documentFile = parent;
        parent = documentFile.getParentFile();
        if (parent == null)
            break;
    }
    final StringBuilder sb = new StringBuilder(volumePath).append(File.separator);
    for (String fileName : fileHierarchy)
        sb.append(fileName).append(File.separator);
    return sb.toString();
}

/**
 * Get the full path of a document from its tree URI.
 *
 * @param treeUri The tree RI.
 * @return The path (without trailing file separator).
 */
public static String getFullPathFromTreeUri(Context context, final Uri treeUri) {
    if (treeUri == null)
        return null;
    String volumePath = getVolumePath(context, getVolumeIdFromTreeUri(treeUri));
    if (volumePath == null)
        return File.separator;
    if (volumePath.endsWith(File.separator))
        volumePath = volumePath.substring(0, volumePath.length() - 1);
    String documentPath = getDocumentPathFromTreeUri(treeUri);
    if (documentPath.endsWith(File.separator))
        documentPath = documentPath.substring(0, documentPath.length() - 1);
    if (documentPath.length() > 0)
        if (documentPath.startsWith(File.separator))
            return volumePath + documentPath;
        else return volumePath + File.separator + documentPath;
    return volumePath;
}

/**
 * Get the path of a certain volume.
 *
 * @param volumeId The volume id.
 * @return The path.
 */
private static String getVolumePath(Context context, final String volumeId) {
    if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP)
        return null;
    try {
        final StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        if (VERSION.SDK_INT >= VERSION_CODES.N) {
            final Class<?> storageVolumeClazz = StorageVolume.class;
            final Method getPath = storageVolumeClazz.getMethod("getPath");
            final List<StorageVolume> storageVolumes = storageManager.getStorageVolumes();
            for (final StorageVolume storageVolume : storageVolumes) {
                final String uuid = storageVolume.getUuid();
                final boolean primary = storageVolume.isPrimary();
                // primary volume?
                if (primary && PRIMARY_VOLUME_NAME.equals(volumeId)) {
                    return (String) getPath.invoke(storageVolume);
                }
                // other volumes?
                if (uuid != null && uuid.equals(volumeId))
                    return (String) getPath.invoke(storageVolume);
            }
            return null;
        }
        final Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
        final Method getVolumeList = storageManager.getClass().getMethod("getVolumeList");
        final Method getUuid = storageVolumeClazz.getMethod("getUuid");
        //noinspection JavaReflectionMemberAccess
        final Method getPath = storageVolumeClazz.getMethod("getPath");
        final Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
        final Object result = getVolumeList.invoke(storageManager);
        final int length = Array.getLength(result);
        for (int i = 0; i < length; i++) {
            final Object storageVolumeElement = Array.get(result, i);
            final String uuid = (String) getUuid.invoke(storageVolumeElement);
            final Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
            // primary volume?
            if (primary && PRIMARY_VOLUME_NAME.equals(volumeId)) {
                return (String) getPath.invoke(storageVolumeElement);
            }
            // other volumes?
            if (uuid != null && uuid.equals(volumeId))
                return (String) getPath.invoke(storageVolumeElement);
        }
        // not found.
        return null;
    } catch (Exception ex) {
        return null;
    }
}

如何在StorageVolume列表和已授予的权限列表之间进行映射?

换句话说,给定一个StorageVolume列表,我如何知道我可以访问哪些,不可以访问哪些,如果我有访问权限,如何打开它并查看其中的内容?

共有3个答案

张溪叠
2023-03-14

适用于API 30(Android 11)

@TargetApi(30)
private fun getVolumePathApi30(context:Context, uuid: String): String{
    // /storage/emulated/0/Android/data/{packageName}/files
    // /storage/0222-9FE1/Android/data/{packageName}/files
    val list = ContextCompat.getExternalFilesDirs(context, null)
.map{ it.canonicalPath.replace(reAndroidDataFolder, "") }

    // /storage/emulated/0
    // /storage/0222-9FE1
    val path = if( uuid == "primary") {
        list.firstOrNull()
    }else {
        list.find { it.contains(uuid, ignoreCase = true) }
    }

    return path ?: error("can't find volume for uuid $uuid")
}
连厉刚
2023-03-14

编辑:找到了一个解决方法,但有一天可能不起作用。

它使用反射来获取StorageVolume实例的真实路径,它使用我之前所拥有的来获取persistedUri权限的路径,如果它们之间有交叉点,就意味着我有访问StorageVolume的权限。

似乎可以在emulator上运行,emulator最终拥有内部存储和SD卡。

希望我们将得到适当的API,而不需要使用反射

如果有更好的方法,没有那些技巧,请告诉我。

所以,这里有一个例子:

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val storageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
        val storageVolumes = storageManager.storageVolumes
        val primaryVolume = storageManager.primaryStorageVolume
        checkAccessButton.setOnClickListener {
            val persistedUriPermissions = contentResolver.persistedUriPermissions
            val storageVolumePathsWeHaveAccessTo = HashSet<String>()
            Log.d("AppLog", "got access to paths:")
            for (persistedUriPermission in persistedUriPermissions) {
                val path = FileUtilEx.getFullPathFromTreeUri(this, persistedUriPermission.uri)
                        ?: continue
                Log.d("AppLog", "path: $path")
                storageVolumePathsWeHaveAccessTo.add(path)
            }
            Log.d("AppLog", "storage volumes:")
            for (storageVolume in storageVolumes) {
                val volumePath = FileUtilEx.getVolumePath(storageVolume)
                if (volumePath == null) {
                    Log.d("AppLog", "storageVolume \"${storageVolume.getDescription(this)}\" - failed to get volumePath")
                } else {
                    val hasAccess = storageVolumePathsWeHaveAccessTo.contains(volumePath)
                    Log.d("AppLog", "storageVolume \"${storageVolume.getDescription(this)}\" - volumePath:$volumePath - gotAccess? $hasAccess")
                }
            }
        }
        requestAccessButton.setOnClickListener {
            val intent = primaryVolume.createOpenDocumentTreeIntent()
            startActivityForResult(intent, 1)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        Log.d("AppLog", "resultCode:$resultCode")
        val uri = data?.data ?: return
        val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
        contentResolver.takePersistableUriPermission(uri, takeFlags)
        val fullPathFromTreeUri = FileUtilEx.getFullPathFromTreeUri(this, uri)
        Log.d("AppLog", "granted uri:$uri $fullPathFromTreeUri")
    }
}

菲留蒂莱克斯。JAVA

/**
 * Get the full path of a document from its tree URI.
 *
 * @param treeUri The tree RI.
 * @return The path (without trailing file separator).
 */
public static String getFullPathFromTreeUri(Context context, final Uri treeUri) {
    if (treeUri == null)
        return null;
    String volumePath = getVolumePath(context, getVolumeIdFromTreeUri(treeUri));
    if (volumePath == null)
        return File.separator;
    if (volumePath.endsWith(File.separator))
        volumePath = volumePath.substring(0, volumePath.length() - 1);
    String documentPath = getDocumentPathFromTreeUri(treeUri);
    if (documentPath.endsWith(File.separator))
        documentPath = documentPath.substring(0, documentPath.length() - 1);
    if (documentPath.length() > 0)
        if (documentPath.startsWith(File.separator))
            return volumePath + documentPath;
        else return volumePath + File.separator + documentPath;
    return volumePath;
}

public static String getVolumePath(StorageVolume storageVolume){
    if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP)
        return null;
    try{
        final Class<?> storageVolumeClazz = StorageVolume.class;
        final Method getPath = storageVolumeClazz.getMethod("getPath");
        return (String) getPath.invoke(storageVolume);
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * Get the path of a certain volume.
 *
 * @param volumeId The volume id.
 * @return The path.
 */
@SuppressLint("ObsoleteSdkInt")
private static String getVolumePath(Context context, final String volumeId) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
        return null;
    try {
        final StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
            final Class<?> storageVolumeClazz = StorageVolume.class;
            //noinspection JavaReflectionMemberAccess
            final Method getPath = storageVolumeClazz.getMethod("getPath");
            final List<StorageVolume> storageVolumes = storageManager.getStorageVolumes();
            for (final StorageVolume storageVolume : storageVolumes) {
                final String uuid = storageVolume.getUuid();
                final boolean primary = storageVolume.isPrimary();
                // primary volume?
                if (primary && PRIMARY_VOLUME_NAME.equals(volumeId)) {
                    return (String) getPath.invoke(storageVolume);
                }
                // other volumes?
                if (uuid != null && uuid.equals(volumeId))
                    return (String) getPath.invoke(storageVolume);
            }
            return null;
        }
        final Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
        final Method getVolumeList = storageManager.getClass().getMethod("getVolumeList");
        final Method getUuid = storageVolumeClazz.getMethod("getUuid");
        //noinspection JavaReflectionMemberAccess
        final Method getPath = storageVolumeClazz.getMethod("getPath");
        final Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
        final Object result = getVolumeList.invoke(storageManager);
        final int length = Array.getLength(result);
        for (int i = 0; i < length; i++) {
            final Object storageVolumeElement = Array.get(result, i);
            final String uuid = (String) getUuid.invoke(storageVolumeElement);
            final Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
            // primary volume?
            if (primary && PRIMARY_VOLUME_NAME.equals(volumeId)) {
                return (String) getPath.invoke(storageVolumeElement);
            }
            // other volumes?
            if (uuid != null && uuid.equals(volumeId))
                return (String) getPath.invoke(storageVolumeElement);
        }
        // not found.
        return null;
    } catch (Exception ex) {
        return null;
    }
}

/**
 * Get the document path (relative to volume name) for a tree URI (LOLLIPOP).
 *
 * @param treeUri The tree URI.
 * @return the document path.
 */
@TargetApi(VERSION_CODES.LOLLIPOP)
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
    final String docId = DocumentsContract.getTreeDocumentId(treeUri);
    //TODO avoid using spliting of a string (because it uses extra strings creation)
    final String[] split = docId.split(":");
    if ((split.length >= 2) && (split[1] != null))
        return split[1];
    else
        return File.separator;
}

/**
 * Get the volume ID from the tree URI.
 *
 * @param treeUri The tree URI.
 * @return The volume ID.
 */
@TargetApi(VERSION_CODES.LOLLIPOP)
private static String getVolumeIdFromTreeUri(final Uri treeUri) {
    final String docId = DocumentsContract.getTreeDocumentId(treeUri);
    final int end = docId.indexOf(':');
    String result = end == -1 ? null : docId.substring(0, end);
    return result;
}

主要活动。xml

<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"
  android:gravity="center" android:orientation="vertical" tools:context=".MainActivity">

  <Button
    android:id="@+id/checkAccessButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="checkAccess"/>

  <Button
    android:id="@+id/requestAccessButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="requestAccess"/>

</LinearLayout>

将其放在一个简单的函数中,如下所示:

/** for each storageVolume, tells if we have access or not, via a HashMap (true for each iff we identified it has access*/
fun getStorageVolumesAccessState(context: Context): HashMap<StorageVolume, Boolean> {
    val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
    val storageVolumes = storageManager.storageVolumes
    val persistedUriPermissions = context.contentResolver.persistedUriPermissions
    val storageVolumePathsWeHaveAccessTo = HashSet<String>()
    //            Log.d("AppLog", "got access to paths:")
    for (persistedUriPermission in persistedUriPermissions) {
        val path = FileUtilEx.getFullPathFromTreeUri(context, persistedUriPermission.uri)
                ?: continue
        //                Log.d("AppLog", "path: $path")
        storageVolumePathsWeHaveAccessTo.add(path)
    }
    //            Log.d("AppLog", "storage volumes:")
    val result = HashMap<StorageVolume, Boolean>(storageVolumes.size)
    for (storageVolume in storageVolumes) {
        val volumePath = FileUtilEx.getVolumePath(storageVolume)
        val hasAccess = volumePath != null && storageVolumePathsWeHaveAccessTo.contains(volumePath)
        result[storageVolume] = hasAccess
    }
    return result
}
张茂勋
2023-03-14

这里有另一种方法可以得到你想要的。这是一种变通方法,就像你在没有使用反射或文件路径的情况下发布的一样。

在模拟器上,我看到了以下我允许访问的项目。

PersistedUriPer0011数组内容(仅URI值):

0 uri=content://com.android.externalstorage.documents/tree/primary:
1 uri=content://com.android.externalstorage.documents/tree/1D03-2E0E:Download
2 uri=content://com.android.externalstorage.documents/tree/1D03-2E0E:
3 uri=content://com.android.externalstorage.documents/tree/primary:DCIM
4 uri=content://com.android.externalstorage.documents/tree/primary:Alarms

":"是冒号 (":"). 因此,URI的构造方式如下:"

uri="content://com.android.externalstorage.documents/tree/

如果uri是卷正下方的目录,则结构为:

uri="content://com.android.externalstorage.documents/tree/

对于结构中更深层次的目录,格式为:

uri="content://com.android.externalstorage.documents/tree/

所以,这只是从这些格式的URI中提取卷的问题。提取的卷可用作StorageManager的密钥。存储卷。下面的代码就是这样做的。

在我看来,应该有一个更简单的方法来解决这个问题。存储卷和URI之间的应用编程接口中肯定缺少链接。我不能说这种技术涵盖了所有情况。

我还质疑storageVolume返回的UUID。uuid似乎是一个32位的值。我认为UUID的长度是128位。这是UUID的替代格式还是从UUID派生的格式?很有趣,这一切就要结束了!:(

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val storageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
        var storageVolumes = storageManager.storageVolumes
        val storageVolumePathsWeHaveAccessTo = HashSet<String>()

        checkAccessButton.setOnClickListener {
            checkAccessToStorageVolumes()
        }

        requestAccessButton.setOnClickListener {
            storageVolumes = storageManager.storageVolumes
            val primaryVolume = storageManager.primaryStorageVolume
            val intent = primaryVolume.createOpenDocumentTreeIntent()
            startActivityForResult(intent, 1)
        }
    }

    private fun checkAccessToStorageVolumes() {
        val storageVolumePathsWeHaveAccessTo = HashSet<String>()
        val persistedUriPermissions = contentResolver.persistedUriPermissions
        persistedUriPermissions.forEach {
            storageVolumePathsWeHaveAccessTo.add(it.uri.toString())
        }
        val storageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
        val storageVolumes = storageManager.storageVolumes

        for (storageVolume in storageVolumes) {
            val uuid = if (storageVolume.isPrimary) {
                // Primary storage doesn't get a UUID here.
                "primary"
            } else {
                storageVolume.uuid
            }
            val volumeUri = uuid?.let { buildVolumeUriFromUuid(it) }
            when {
                uuid == null -> 
                    Log.d("AppLog", "UUID is null for ${storageVolume.getDescription(this)}!")
                storageVolumePathsWeHaveAccessTo.contains(volumeUri) -> 
                    Log.d("AppLog", "Have access to $uuid")
                else -> Log.d("AppLog", "Don't have access to $uuid")
            }
        }
    }

    private fun buildVolumeUriFromUuid(uuid: String): String {
        return DocumentsContract.buildTreeDocumentUri(
            "com.android.externalstorage.documents",
            "$uuid:"
        ).toString()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        Log.d("AppLog", "resultCode:$resultCode")
        val uri = data?.data ?: return
        val takeFlags =
            Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
        contentResolver.takePersistableUriPermission(uri, takeFlags)
        Log.d("AppLog", "granted uri: ${uri.path}")
    }
}
 类似资料:
  • 我正在建立一个网站,我正在使用Spotify应用编程接口作为音乐库。我想添加更多的过滤器和订单选项来搜索Traks比api允许我,所以我想知道什么轨道/歌曲数据可以从API保存到我的数据库,如艺术家名称或流行度。 我想保存:姓名、艺术家、专辑和其他一些东西。这是可能的还是违反了条款和条件? 提前感谢!

  • 问题内容: 我有一个包装两栏式布局的古老问题。我的侧边栏处于浮动状态,因此我的容器无法包装内容和侧边栏。 似乎有许多方法可以解决Firefox中的明显错误: 在我的情况下,似乎唯一可以正常工作的解决方案是解决方案,这有点麻烦。给我带来讨厌的滚动条,并且肯定有副作用。另外,由于它的不正确行为,IE7显然不应该遭受此问题的困扰,但就我而言,它与Firefox一样遭受痛苦。 我们目前可以使用哪种方法最可

  • 本文向大家介绍vim 我可以使用哪些选项?,包括了vim 我可以使用哪些选项?的使用技巧和注意事项,需要的朋友参考一下 示例 如果您不知道应该使用哪些选项,则可能对该:options命令感兴趣。 这将打开一个列出所有Vim选项并显示其当前值的拆分。有26个部分显示您可以尝试的所有选项。 例如 在值行(例如set nowrap)上,您可以按CR切换值(如果它是二进制值)。在选项行(例如wrap lo

  • 本文向大家介绍CSS选择器有哪些?哪些属性可以继承?相关面试题,主要包含被问及CSS选择器有哪些?哪些属性可以继承?时的应答技巧和注意事项,需要的朋友参考一下 选择器 通配符 id class 标签 后代选择器 子选择器 兄弟选择器 属性选择器 伪类选择器 伪元素选择器 可以继承的属性 font-size font-weight font-style font-family color

  • 本文向大家介绍CSS选择符有哪些?哪些属性可以继承?相关面试题,主要包含被问及CSS选择符有哪些?哪些属性可以继承?时的应答技巧和注意事项,需要的朋友参考一下 1.id选择器( # myid) 2.类选择器(.myclassname) 3.标签选择器(div, h1, p) 4.相邻选择器(h1 + p) 5.子选择器(ul > li) 6.后代选择器(li a) 7.通配符选择器( * ) 8.

  • 问题内容: TL; DR; 我正在寻找一个可以查找特定中间操作或终端操作的地方。在哪里可以找到此类文档? 编辑 这不是如何确保java8流中的处理顺序的重复项?,因为该问题未提供完整的操作列表。 该软件包的文件说: 流是否具有遇到顺序取决于源和中间操作 为了确保在整个流操作中维持顺序,您必须研究流源,所有中间操作和终端操作的文档,以了解它们是否维持顺序(或源是否在第一个顺序中具有顺序)地点)。 一