Android系统目录树建立过程

2023-11-04

一、文件系统类型

        了解Android系统目录树的建立之前,有必要了解文件系统类型。Linux内核中将文件系统类型抽象为结构体struct file_system_type,其中name为文件系统名称,例如ext4、f2fs、rootfs等;mount()\mount2()是挂载文件系统时调用的接口,用于创建super_block,并返回根目录;kill_sb()在卸载文件系统时调用,做一些清理工作;next指向下一个文件系统类型。

struct file_system_type {
	const char *name;
	int fs_flags;
    ................................................
	struct dentry *(*mount) (struct file_system_type *, int,
		       const char *, void *);
	struct dentry *(*mount2) (struct vfsmount *, struct file_system_type *, int,
			       const char *, void *);
	void *(*alloc_mnt_data) (void);
	void (*kill_sb) (struct super_block *);
	struct module *owner;
	struct file_system_type * next;
	struct hlist_head fs_supers;
    ...............................................
};

        所有注册到内核的文件系统类型,都放在以file_systems为表头的单链表中。register_filesystem()就是向该链表中加入新的元素;unregister_filesystem()就是将对应的文件系统类型从该链表中删除;get_fs_type()就是根据文件系统名称在链表中查找。

        常见文件系统类型,都是在对应模块初始化时注册的,比如ext4在模块初始化时注册ext4_fs_type。

static int __init ext4_init_fs(void)
{
	int i, err;

	ratelimit_state_init(&ext4_mount_msg_ratelimit, 30 * HZ, 64);
	ext4_li_info = NULL;
	mutex_init(&ext4_li_mtx);

	/* Build-time check for flags consistency */
	................................................
	register_as_ext3();
	register_as_ext2();
	err = register_filesystem(&ext4_fs_type);
	................................................
}

        通过cat /proc/filesystems节点查看系统中所有注册的文件系统类型名称。

二、根目录的创建

        进程的路径信息保存在task_struct成员fs_struct *fs指向的结构体中,其中root为根目录,pwd为当前目录。fs_struct *fs的数据来源于父进程,当clone_flags的CLONE_FS置位时,父子进程指向同一个fs_struct指针,否则创建一个fs_struct,并把父进程的信息拷贝过来。

struct fs_struct {
    ......................
	struct path root, pwd;
};
static int copy_fs(unsigned long clone_flags, struct task_struct *tsk)
{
	struct fs_struct *fs = current->fs;
	if (clone_flags & CLONE_FS) {
		/* tsk->fs is already what we want */
		spin_lock(&fs->lock);
		if (fs->in_exec) {
			spin_unlock(&fs->lock);
			return -EAGAIN;
		}
		fs->users++;
		spin_unlock(&fs->lock);
		return 0;
	}
	tsk->fs = copy_fs_struct(fs);
	if (!tsk->fs)
		return -ENOMEM;
	return 0;
}

        init_task是所有进程中的老祖宗,其它进程的fs_struct *fs都直接或间接来源与init_task的fs_struct *fs,该指针指向的结构体是在start_kernel()-->vfs_caches_init()-->mnt_init()-->init_mount_tree()中初始化的。

static void __init init_mount_tree(void)
{
	struct vfsmount *mnt;
	struct mnt_namespace *ns;
	struct path root;
	struct file_system_type *type;

	type = get_fs_type("rootfs");
	if (!type)
		panic("Can't find rootfs type");
	mnt = vfs_kern_mount(type, 0, "rootfs", NULL);
	put_filesystem(type);
	if (IS_ERR(mnt))
		panic("Can't create rootfs");

	ns = create_mnt_ns(mnt);
	if (IS_ERR(ns))
		panic("Can't allocate initial namespace");

	init_task.nsproxy->mnt_ns = ns;
	get_mnt_ns(ns);

	root.mnt = mnt;
	root.dentry = mnt->mnt_root;
	mnt->mnt_flags |= MNT_LOCKED;

	set_fs_pwd(current->fs, &root);                //设置为init_task 的当前目录
	set_fs_root(current->fs, &root);                //设置为init_task 的根目录
}

        vfs_kern_mount()首先调用alloc_vfsmnt()分配并初始一个struct mount结构体,其成员mnt_devname初始化为rootfs,然后调用mount_fs()获取rootfs文件系统的根目录。

struct vfsmount *
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
	struct mount *mnt;
	struct dentry *root;

	if (!type)
		return ERR_PTR(-ENODEV);

	mnt = alloc_vfsmnt(name);
	if (!mnt)
		return ERR_PTR(-ENOMEM);

	................................................................................

	root = mount_fs(type, flags, name, &mnt->mnt, data);
	if (IS_ERR(root)) {
		mnt_free_id(mnt);
		free_vfsmnt(mnt);
		return ERR_CAST(root);
	}

	mnt->mnt.mnt_root = root;
	mnt->mnt.mnt_sb = root->d_sb;
	mnt->mnt_mountpoint = mnt->mnt.mnt_root;
	mnt->mnt_parent = mnt;
	lock_mount_hash();
	list_add_tail(&mnt->mnt_instance, &root->d_sb->s_mounts);
	unlock_mount_hash();
	return &mnt->mnt;
}

         mount_fs()调用对应文件系统类型的mount接口,来创建对应文件系统的super_block和根目录。这里的文件系统类型为rootfs,其对应的mount接口为rootfs_mount()。

struct dentry *
mount_fs(struct file_system_type *type, int flags, const char *name, struct vfsmount *mnt, void *data)
{
	struct dentry *root;
	struct super_block *sb;
	char *secdata = NULL;
	int error = -ENOMEM;
    .......................................................
	if (type->mount2)
		root = type->mount2(mnt, type, flags, name, data);
	else
		root = type->mount(type, flags, name, data);
	if (IS_ERR(root)) {
		error = PTR_ERR(root);
		goto out_free_secdata;
	}
	sb = root->d_sb;
	BUG_ON(!sb);
	WARN_ON(!sb->s_bdi);
	sb->s_flags |= MS_BORN;
    .......................................................
}
static struct file_system_type rootfs_fs_type = {
	.name		= "rootfs",
	.mount		= rootfs_mount,
	.kill_sb	= kill_litter_super,
};
static struct dentry *rootfs_mount(struct file_system_type *fs_type,
	int flags, const char *dev_name, void *data)
{
	static unsigned long once;
	void *fill = ramfs_fill_super;

	if (test_and_set_bit(0, &once))
		return ERR_PTR(-ENODEV);

	if (IS_ENABLED(CONFIG_TMPFS) && is_tmpfs)
		fill = shmem_fill_super;

	return mount_nodev(fs_type, flags, data, fill);
}

        mount_nodev()调用sget()查找或创建一个super_block,调用fill_super填充super_block数据,包括创建根目录赋值给super_block->s_root。fill_super对应的是ramfs_fill_super。

struct dentry *mount_nodev(struct file_system_type *fs_type,
	int flags, void *data,
	int (*fill_super)(struct super_block *, void *, int))
{
	int error;
	struct super_block *s = sget(fs_type, NULL, set_anon_super, flags, NULL);

	if (IS_ERR(s))
		return ERR_CAST(s);

	error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
	if (error) {
		deactivate_locked_super(s);
		return ERR_PTR(error);
	}
	s->s_flags |= MS_ACTIVE;
	return dget(s->s_root);
}

        ramfs_fill_super()-->d_make_root()-->__d_alloc(struct super_block *sb, const struct qstr *name)的参数name为空时,将以"/"作为目录名,这就是根目录"/"的由来

int ramfs_fill_super(struct super_block *sb, void *data, int silent)
{
	struct ramfs_fs_info *fsi;
	struct inode *inode;
	int err;
    .........................................................
	sb->s_maxbytes		= MAX_LFS_FILESIZE;
	sb->s_blocksize		= PAGE_SIZE;
	sb->s_blocksize_bits	= PAGE_SHIFT;
	sb->s_magic		= RAMFS_MAGIC;
	sb->s_op		= &ramfs_ops;
	sb->s_time_gran		= 1;

	inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
	sb->s_root = d_make_root(inode);
	if (!sb->s_root)
		return -ENOMEM;

	return 0;
}
struct dentry *d_make_root(struct inode *root_inode)
{
	struct dentry *res = NULL;

	if (root_inode) {
		res = __d_alloc(root_inode->i_sb, NULL);
                 
        ......................................................
	}
	return res;
}
struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
{
	struct dentry *dentry;
	char *dname;
	int err;

	dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);
	if (!dentry)
		return NULL;
    ...........................................................
	dentry->d_iname[DNAME_INLINE_LEN-1] = 0;
	if (unlikely(!name)) {
		static const struct qstr anon = QSTR_INIT("/", 1);
		name = &anon;
		dname = dentry->d_iname;
	} else if (name->len > DNAME_INLINE_LEN-1) {
        .......................................................
	} else  {
		dname = dentry->d_iname;
	}	
    ...........................................................
	return dentry;
}

        根目录的结构体关系可以简化如下,init_task的成员fs指向结构体fs_struct,fs_struct成员保存了根目录路径struct path,struct path的成员dentry指向rootfs文件系统根目录,根目录的名称为"/",成员mnt指向vfsmount结构体,vfsmount的成员mnt_root指向根目录,vfsmount包含于结构体mount中。

三、子目录的创建

        Android中目录初始化是在init进程中完成的。一部分是在init进程first_stage阶段创建目录并挂载文件系统,另一部分是解析fstab文件,根据文件配置完成分区挂载。

mkdir("/dev", 0755);  
mkdir("/proc", 0755);  
mkdir("/sys", 0755);  
  
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");  
mkdir("/dev/pts", 0755);  
mkdir("/dev/socket", 0755);  
mount("devpts", "/dev/pts", "devpts", 0, NULL);  
mount("proc", "/proc", "proc", 0, NULL);  
mount("sysfs", "/sys", "sysfs", 0, NULL); 

on fs  
    write /proc/bootprof "INIT:Mount_START"  
    mount_all /fstab.mt6580

        这里先介绍子目录的创建,下一节介绍文件系统挂载。创建目录的系统调用是mkdir(),mkdir()调用到sys_mkdirat()。sys_mkdirat()首先调用 user_path_create(),该函数执行完毕后,父目录保存在参数struct path *path中,返回一个dentry指针,dentry->d_name保存了目录名以及对应的哈希值。

SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode)  
{  
    return sys_mkdirat(AT_FDCWD, pathname, mode);  
}  

SYSCALL_DEFINE3(mkdirat, int, dfd, const char __user *, pathname, umode_t, mode)  
{  
    struct dentry *dentry;  
    struct path path;  
    int error;  
    unsigned int lookup_flags = LOOKUP_DIRECTORY;  
  
retry:  
    dentry = user_path_create(dfd, pathname, &path, lookup_flags);  
    if (IS_ERR(dentry))  
        return PTR_ERR(dentry);  
  
    if (!IS_POSIXACL(path.dentry->d_inode))  
        mode &= ~current_umask();  
    error = security_path_mkdir(&path, dentry, mode);  
    if (!error)  
        error = vfs_mkdir2(path.mnt, path.dentry->d_inode, dentry, mode);  
    done_path_create(&path, dentry);  
    if (retry_estale(error, lookup_flags)) {  
        lookup_flags |= LOOKUP_REVAL;  
        goto retry;  
    }  
    return error;  
}  

        user_path_create()-->filename_create()-->__lookup_hash()调用lookup_dcache()在hash表dentry_hashtable中查找,如果找到就返回。如果没有找到,就调用d_alloc()分配一个dentry,然后调用lookup_real()-->dir->i_op->lookup()在父目录数据块中查找是否有对应名字的目录,如果有会初始化dentry的d_inode成员。

static struct dentry *__lookup_hash(const struct qstr *name,
		struct dentry *base, unsigned int flags)
{
	struct dentry *dentry = lookup_dcache(name, base, flags);

	if (dentry)
		return dentry;

	dentry = d_alloc(base, name);
	if (unlikely(!dentry))
		return ERR_PTR(-ENOMEM);

	return lookup_real(base->d_inode, dentry, flags);
}
static struct dentry *lookup_real(struct inode *dir, struct dentry *dentry,
				  unsigned int flags)
{
	struct dentry *old;

	/* Don't create child dentry for a dead directory. */
	if (unlikely(IS_DEADDIR(dir))) {
		dput(dentry);
		return ERR_PTR(-ENOENT);
	}

	old = dir->i_op->lookup(dir, dentry, flags);
	if (unlikely(old)) {
		dput(dentry);
		dentry = old;
	}
	return dentry;
}

        sys_mkdirat()再调用vfs_mkdir2(),该函数中先确认目录是否已经创建(d_inode是否为空),如果已经创建则返回。如果没有创建,则通过dir->i_op->mkdir(dir, dentry, mode)创建目录。

int vfs_mkdir2(struct vfsmount *mnt, struct inode *dir, struct dentry *dentry, umode_t mode)
{
	int error = may_create(mnt, dir, dentry);
	unsigned max_links = dir->i_sb->s_max_links;
	if (error)
		return error;
	if (!dir->i_op->mkdir)
		return -EPERM;
	mode &= (S_IRWXUGO|S_ISVTX);
	error = security_inode_mkdir(dir, dentry, mode);
	if (error)
		return error;
	if (max_links && dir->i_nlink >= max_links)
		return -EMLINK;
	error = dir->i_op->mkdir(dir, dentry, mode);
	if (!error)
		fsnotify_mkdir(dir, dentry);
	return error;
}

        结构体关系可以简化如下,子目录的dentry都通过d_child链入到父目录dentry的d_subdirs中。已经打开目录的dentry,会通过d_hash成员链入到hash表dentry_hashtable中,hash值由父目录指针和文件/目录名称构造而成。

四、挂载设备

        挂载文件系统的调用是mount,int mount(const char *source, const char *target,const char *filesystemtype, unsigned long mountflags, const void *data)。参数source:将要挂载的文件系统,通常是一个设备名,或者文件名;target:文件系统要挂载的目标目录;filesystemtype:文件系统的类型,例如“ext2”、”ext4”、”proc”等;mountflags指定文件系统的读写访问标志,例如MS_RDONLY、MS_REMOUNT等;data:某些文件系统特有的参数。mount成功执行时,返回0,失败返回 -1。

        内核代码从SYSCALL_DEFINE5(mount)-->do_mount()-->do_new_mount()开始跟踪,vfs_kern_mount()上面已经分析过,是用于创建文件系统的super_block和根目录。

static int do_new_mount(struct path *path, const char *fstype, int flags,
			int mnt_flags, const char *name, void *data)
{
	struct file_system_type *type;
	struct vfsmount *mnt;
	int err;

	if (!fstype)
		return -EINVAL;

	type = get_fs_type(fstype);
	if (!type)
		return -ENODEV;

	mnt = vfs_kern_mount(type, flags, name, data);
	if (!IS_ERR(mnt) && (type->fs_flags & FS_HAS_SUBTYPE) &&
	    !mnt->mnt_sb->s_subtype)
		mnt = fs_set_subtype(mnt, fstype);
    ..............................................................
	err = do_add_mount(real_mount(mnt), path, mnt_flags);
	if (err)
		mntput(mnt);
	return err;
}

        do_add_mount()-->lock_mount()搜索挂载目标路径的mountpoint,如果目标目录没有被挂载过,直接用该目录创建mountpoint;如果目标目录被挂载过,甚至重复挂载,要一直查到最后一个被挂载的文件系统根目录,获得目录后再创建mountpoint。hash表mountpoint_hashtable,以dentry为键值存放dentry对应的mountpoint,创建mountpoint时先在该hash表中查找,如果没找到就创建一个。

static int do_add_mount(struct mount *newmnt, struct path *path, int mnt_flags)
{
	struct mountpoint *mp;
	struct mount *parent;
	int err;

	mnt_flags &= ~MNT_INTERNAL_FLAGS;

	mp = lock_mount(path);
	if (IS_ERR(mp))
		return PTR_ERR(mp);

	parent = real_mount(path->mnt);
	err = -EINVAL;
    ..........................................................................
	newmnt->mnt.mnt_flags = mnt_flags;
	err = graft_tree(newmnt, parent, mp);

unlock:
	unlock_mount(mp);
	return err;
}
static struct mountpoint *lock_mount(struct path *path)
{
	struct vfsmount *mnt;
	struct dentry *dentry = path->dentry;
retry:
	inode_lock(dentry->d_inode);
	if (unlikely(cant_mount(dentry))) {
		inode_unlock(dentry->d_inode);
		return ERR_PTR(-ENOENT);
	}
	namespace_lock();
	mnt = lookup_mnt(path);
	if (likely(!mnt)) {
		struct mountpoint *mp = get_mountpoint(dentry);
	         
        ...............................................................................
		return mp;
	}
	namespace_unlock();
	inode_unlock(path->dentry->d_inode);
	path_put(path);
	path->mnt = mnt;
	dentry = path->dentry = dget(mnt->mnt_root);
	goto retry;
}

        do_add_mount()-->graft_tree()-->attach_recursive_mnt()先调用mnt_set_mountpoint()建立起子mount与父mount之间的关系,再调用commit_tree()-->__attach_mnt()将子mount加入到hash表mount_hashtable中,该哈希表的键值由父mount和挂载目标目录组成。在路径搜索的过程中,follow_managed()会在mount_hashtable中查找当前目录是否有对应的子mount,如果有进入到子mount的根目录,从而实现了路径的跳转。

static int attach_recursive_mnt(struct mount *source_mnt,
			struct mount *dest_mnt,
			struct mountpoint *dest_mp,
			struct path *parent_path)
{
	HLIST_HEAD(tree_list);
	struct mnt_namespace *ns = dest_mnt->mnt_ns;
	struct mountpoint *smp;
	struct mount *child, *p;
	struct hlist_node *n;
	int err;
                 
    .............................................................................
	smp = get_mountpoint(source_mnt->mnt.mnt_root);
	if (IS_ERR(smp))
		return PTR_ERR(smp);
                 
    .............................................................................
	if (parent_path) {
		detach_mnt(source_mnt, parent_path);
		attach_mnt(source_mnt, dest_mnt, dest_mp);
		touch_mnt_namespace(source_mnt->mnt_ns);
	} else {
		mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
		commit_tree(source_mnt);
	}
                 
    ..............................................................................
	return 0;
}

static int follow_managed(struct path *path, struct nameidata *nd)
{
	struct vfsmount *mnt = path->mnt; /* held by caller, must be left alone */
	unsigned managed;
	bool need_mntput = false;
	int ret = 0;
    ..............................................................
	while (managed = ACCESS_ONCE(path->dentry->d_flags),
	       managed &= DCACHE_MANAGED_DENTRY,
	       unlikely(managed != 0)) {
         
                                  
        ..........................................................
		if (managed & DCACHE_MOUNTED) {
			struct vfsmount *mounted = lookup_mnt(path);
			if (mounted) {
				dput(path->dentry);
				if (need_mntput)
					mntput(path->mnt);
				path->mnt = mounted;
				path->dentry = dget(mounted->mnt_root);
				need_mntput = true;
				continue;
			}
                                                     
            .....................................................
		}
                                   
        ........................................................
		break;
	}
    ........................................................
	return ret;
}

        以proc为例,mount后的结构体关系简化如下。子mount的mnt_mountpoint成员指向挂载目录,mnt_parent指向挂载目录所在mount。子mount以挂载目录指针为键值存放在hash表mount_hashtable中。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android系统目录树建立过程 的相关文章

  • 这个方法比 Math.random() 更快吗?

    我是一名初学者 目前已经开始开发一款使用粒子群优化算法的 Android 游戏 我现在正在尝试稍微优化我的代码 并且 for 循环中有相当多的 Math random 几乎一直在运行 所以我正在考虑一种方法来绕过并跳过所有 Math ran
  • 适用于 IOS 和 Android 的支付网关 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在开发一个应用程序 用户必须在澳大利亚餐馆通过应用程序 android ios 付款 有两种付款方式 通过 PayPal 或 Visa
  • Android 应用程序在启动时打开应用程序信息屏幕,而不是启动主 Activity

    我不确定这是否是一个问题 但这是我第一次遇到这个问题 我正在开发一个应用程序 当我在进行一些编码后断开应用程序与 Android Studio 和 PC 的连接时 如果我尝试在手机上打开应用程序 它会启动app info屏幕 我们看到强制停
  • 需要使用手机后退按钮返回 Web 视图的帮助

    这是我的代码 package com testappmobile import android app Activity import android os Bundle import android view KeyEvent impor
  • Android:我可以创建一个不是矩形的视图/画布吗?圆形的?

    我有一个圆形视图 悬停在主要内容上方 gt 从屏幕出来的 z 轴方向 当有人点击屏幕时 我希望选择主要内容或悬停在上方的视图 当它覆盖主视图时 到目前为止效果很好 我在透明画布上有一个圆形物品 这意味着您可以看到该圆圈之外的背景的所有内容
  • 如何在活动中的必填字段中显示 * 符号

    我需要在活动中的必填字段中显示 符号 你能建议我怎样才能做到这一点吗 任何帮助 将不胜感激 我想说 作为必填字段的标记不遵循本机 Android 主题 的组合setHint and setError对于 Android 应用程序来说看起来更
  • 不变违规:requireNativeComponent:在 UIManager 中找不到“RNSVGSvgViewAndroid”

    我对标题中提到的错误感到头疼 我正在使用react native gifted charts https www npmjs com package react native gifted charts v 1 0 3 https www
  • 当不支持 Google Play 应用内结算 V.3 时

    在 Google Play 的应用内结算 V 3 中 有一个选项可以检查用户设备是否支持它 使用是否支持计费 http developer android com google play billing versions html meth
  • 在选项卡上保存数据

    我有 3 个选项卡 每个选项卡都有一个单独的活动 我想在用户单击任一选项卡上的 保存 时保存数据 有几个选项可供选择 共享首选项 全局变量或将对象保存在上下文中 编辑 我必须保存图像和文本字段 Android 共享首选项 https sta
  • 来自相机的 MediaCodec 视频流方向和颜色错误

    我正在尝试流式传输视频捕获直接从相机适用于 Android 设备 到目前为止 我已经能够从 Android 相机捕获每一帧预览帧 byte data Camera camera 函数 对数据进行编码 然后成功解码数据并显示到表面 我用的是安
  • android中listview显示数据库中的数据

    我是安卓新手 我想知道如何在列表视图中显示数据库中的数据 它不会向数据库添加数据 我只是显示我们存储在数据库中的任何内容 请帮助我实现这一目标 提前致谢 使用这些课程可能会对您有所帮助 用于数据库创建 package com example
  • 有多少种方法可以将位图转换为字符串,反之亦然?

    在我的应用程序中 我想以字符串的形式将位图图像发送到服务器 我想知道有多少种方法可以将位图转换为字符串 现在我使用 Base64 格式进行编码和解码 它需要更多的内存 是否有其他可能性以不同的方式做同样的事情 从而消耗更少的内存 现在我正在
  • Android 时钟滴答数 [赫兹]

    关于 proc pid stat 中应用程序的总 CPU 使用率 https stackoverflow com questions 16726779 total cpu usage of an application from proc
  • 如何构建自定义摄像机应用程序?

    我正在尝试开发一个自定义摄像机录像机 当我的设备在 Activity 的 beginRecording 中执行 start MediaRecorder 方法时 应用程序崩溃 我不知道出了什么问题 因为我遵循谷歌API指南 http deve
  • Android Gradle 同步失败:无法解析配置“:classpath”的所有工件

    错误如下 Caused by org gradle api internal artifacts ivyservice DefaultLenientConfiguration ArtifactResolveException Could n
  • 在android中创建SQLite数据库

    我想在我的应用程序中创建一个 SQLite 数据库 其中包含三个表 我将向表中添加数据并稍后使用它们 但我喜欢保留数据库 就好像第一次安装应用程序时它会检查数据库是否存在 如果存在则更新它 否则如果不存在则创建一个新数据库 此外 我正在制作
  • Android AdMob:addView 在返回活动之前不会显示广告

    我正在尝试在游戏顶部添加横幅广告 我的活动使用带有自定义 SurfaceView 的relativelayout 我希望广告与 SurfaceView 重叠 广告会加载并可点击 但不会绘制到屏幕上 当我离开活动并返回时 会绘制广告 例如 通
  • 在 Android 手机中通过耳机插孔发送数据

    我目前正在处理一个新项目 我必须通过具有特定电压的耳机插孔发送数据 然后我可以在该电压上工作 所以这里我需要根据我的数据来编程具体电压 我是否可以在android中访问耳机的输出电压 然后创建一个应用程序来控制该电压 这是一篇讨论此问题的
  • 使用Intent拨打电话需要权限吗?

    在我的一个应用程序中 我使用以下代码来拨打电话 Intent intent new Intent Intent ACTION CALL Uri parse startActivity intent 文档说我确实需要以下清单许可才能这样做
  • 如何正确编写AttributeSet的XML?

    我想创建一个面板适用于 Android 平台的其他小部件 http code google com p android misc widgets 在运行时 XmlPullParser parser getResources getXml R

随机推荐

  • 第六章 PCB 的 DRC 检查、拼版设计及资料输出

    目录 第六章 PCB 的 DRC 检查 拼版设计及资料输出 6 1 DRC 的检查及丝印的调整 6 2 拼板介绍 6 3 V Cut 和邮票孔的概念 6 4 拼板的实战演示 6 5 Gerber 文件的输出及整理 第六章 PCB 的 DRC
  • 记一次失败的regeorg+proxifiler代理实验--解决内网主机不出网问题

    记一次失败的regeorg proxifiler代理实验 解决内网主机不出网问题 一 简述 二 环境 三 流程 3 1 种webshell 3 2 启动regeorg 3 3 配置proxifiler 3 3 1 配置代理服务器 3 3 2
  • UNI-APP_横屏切换竖屏出现样式混乱问题

    app从竖屏页面1进入竖屏页面2 再进入横屏 再返回 再返回从新回到竖屏页面1 再次进入竖屏页面2 发现竖屏页面2的所有图片字体都被放大了 再返回竖屏1 再进入竖屏2 一切又恢复正常 解决跳转横屏竖屏样式放大错乱问题 解决方法 不要使用un
  • 前端部署:vue跨域配置、打包、nginx本机&远程访问

    vue版本 npm版本 2 6 11 8 1 2 前排感谢大佬 最全vue打包前后的跨域问题 绝对解决你的问题 0 项目说明 服务器和访问其的电脑同属校园网内网中 1 vue跨域配置 两种跨域方式均可 这里采用跨域2 1 1 vue con
  • 【Java】list对象(类)按某个属性排序

    这里采用的方法是 将要排序的类实现Comparable接口 具体如下 假设有个rule类 要按照sort字段排序 首先该类要实现Comparable接口 并实现它的compareTo 方法 Author EvanChen Date 2018
  • java连接kafka测试

    进入到kafka文件夹中修改配置文件 vim config server properties 启动zookeeper bin zookeeper server start sh config zookeeper properties 端口
  • python mssql数据库开发_Python实现的连接mssql数据库操作示例

    本文实例讲述了Python实现的连接mssql数据库操作 分享给大家供大家参考 具体如下 1 目标数据sql2008 R2 ComPrject gt TestModel 2 安装python 连接mssql 模块 运行 pip instal
  • 小程序hover-class点击态效果——小程序体验

    微信小程序设置 hover class 实现点击态效果 增强小程序触感 提高用户交互感知度 概念及注意事项 微信小程序中 可以用 hover class 属性来指定元素的点击态效果 但是在在使用中要注意 大部分组件是不支持该属性的 目前支持
  • Spring Cache

    Spring Cache Spring Cache使用方法与Spring对事务管理的配置相似 Spring Cache的核心就是对某个方法进行缓存 其实质就是缓存该方法的返回结果 并把方法参数和结果用键值对的方式存放到缓存中 当再次调用该方
  • Cisco Voip实验

    http hackerjx blog 51cto com 383839 248031 转载于 https blog 51cto com markyan 1043695
  • Java基础-I/O流(文件字节流)

    字符串常见的字符底层组成是什么样的 英文和数字等在任何国家的字符集中都占1个字节 GBK字符中一个中文字符占2个字节 UTF 8编码中一个中文字符占3个字节 注意 编码前的字符集和编码好的字符集要必须一致 否则会出现中文字符乱码 英文和数字
  • NullPointerException : HiveAuthorizerImpl.checkPrivileges(HiveAuthorizerImpl.java:85)

    背景 做hive sentry LDAP授权 1 jdbc hive2 localhost 10000 gt connect jdbc hive2 localhost 10000 Connecting to jdbc hive2 local
  • Gitlab中Pipeline语法三

    Pipeline语法三 only except rules workflow only和except 用分支策略来限制jobs构建 only 定义哪些分支和标签的git项目将会被job执行 except定义哪些分支和标签的git项目将不会被
  • 腾讯云对象存储的创建和S3 Browser的使用

    简述 想想第一次接触对象存储的时候还是很兴奋的 同时也是一脸懵逼 然后开始网上疯狂的找资料 但因为客户当时给的文档写的是关于Amazon S3之类的 所以自以为的就只有Amazon S3这一家 接着开始查资料 经过一番努力最后在Amazon
  • ubuntu下下载安装dstat

    如果直接安装采用s sudo apt install dstat 或者通过下载方式 https launchpad net ubuntu source dstat 0 7 4 6 1 下载这三个包 然后里面直接运行 dstat就可以了 前提
  • I3Net: Implicit Instance-Invariant Network for Adapting One-Stage Object Detectors

    摘要 最近的两阶段跨域检测工作广泛探索了局部特征模式 以获得更准确的适配结果 这些方法在很大程度上依赖于区域建议机制和基于ROI的实例级特性来设计关于前景目标的细粒度特性对齐模块 然而 对于一阶段检测检测器 在检测流程中很难甚至不可能获得显
  • 【Scapy】使用Python的Scapy包对Wirshark捕获的Http数据包进行解析(格式输出)

    通过Anaconda安装scapy pip install scapy http Python源码如下 实现功能 1 读取本地pcap文件 文件内容为Wirshark捕获的数据二进制流 2 通过scapy将二进制数据流解析为有结构的pake
  • 贪心算法解决集合覆盖问题

    问题描述 假设存在下面需要付费的广播台 以及广播台需要覆盖的地区 如何选择最少的广播台 让所有的地区都可以接受到信号 广播台 覆盖地区 k1 北京 上海 天津 k2 广州 北京 深圳 k3 成都 上海 杭州 k4 上海 天津 k5 杭州 大
  • python折线图设置标题

    在 Python 中使用 Matplotlib 绘制折线图时 可以使用 title 函数来设置图表的标题 例如 import matplotlib pyplot as plt 绘制折线图 plt plot x y 设置标题 plt titl
  • Android系统目录树建立过程

    一 文件系统类型 了解Android系统目录树的建立之前 有必要了解文件系统类型 Linux内核中将文件系统类型抽象为结构体struct file system type 其中name为文件系统名称 例如ext4 f2fs rootfs等