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系统目录树建立过程 的相关文章

  • 更改操作栏标题文本颜色

    我正在尝试更改 ActionBar 中标题文本的颜色 但我似乎无法让它工作 这是我尝试使用的风格 在我的应用程序主题中我使用titleTextStyle
  • 相当于Android中的javax.swing.Timer

    有没有类似的东西javax swing Timer在安卓上 我知道如何创建自己的线程 但是有类似摆动计时器的东西吗 您可能正在寻找课程android os CountDownTimer http developer android com
  • Android Studio磁盘空间不足如何解决?

    我随机收到此错误 并且不确定为什么 Android Studio 的磁盘空间变低 Android Studio 系统目录分区磁盘空间不足 Update 此应用程序安装在运行 10 10 1 的 Mac 上 具有 251GB 内部存储和占用
  • 如何在 android-studio 0.3.6 中运行 Gradle 1.9?

    我只是花了一些时间尝试将现有的 android studio 项目从 gradle 1 8 迁移到 gradle 1 9 Final 昨天发布 但失败了19th Nov 我在这里阅读了大多数其他与 gradle 相关的帖子 但没有一个对我有
  • Android 上的硬币识别

    我目前正在开发一个 Android 应用程序 它能够拍摄硬币的现有图像 或者使用内置摄像头扫描单个硬币 非常像 Google Goggles 我正在使用 Android 版 OpenCV 我的问题如下 什么方法最适合使用 OpenCV 在
  • 用于代码生成的 ANTLR 工具版本 4.7.1 与当前运行时版本 4.5.3 不匹配

    我正在开发一个 Android 应用程序 当前使用 DSL 和一些库 突然构建给了我这个错误 任务 app kaptDebugKotlin 失败 用于代码生成的 ANTLR 工具版本 4 7 1 与当前运行时版本 4 5 3 不匹配 用于解
  • 如何在Firebase Android应用程序中分离两个不同的用户?

    我有一个应用程序 有两种不同类型的用户 一种是教师 第二种是普通用户 如果普通会员登录 他会去normal memberActivity如果他是教师会员 他会去Teacher memberActivity 我如何在登录活动中执行此操作 我的
  • 如何从debug.keystore文件获取MD5?

    我使用一些命令来获取 MD5 私钥debug keystore文件 但实际上我得到的是 SHA1 私钥而不是 MD5 我不知道如何获得MD5 这是我使用的命令 keytool list alias androiddebugkey keyst
  • Android 依赖项:apklib 与 aar 文件

    据我了解 apklib包含代码 共享资源Maven aar文件由以下人员分发Gradle The aar与 apklib 的主要区别在于 类被编译并包含在 aar 根目录下的classes jar 中 然而apklib不能包含已编译的类文件
  • 如何在Android中隐藏应用程序标题? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我想隐藏应用程序标题栏 您可以通过编程来完成 import android app Activity import android os
  • EditText 的高度不会扩展到其父级的高度

    我在滚动视图中放置了编辑文本 高度 match parent并期望它的高度等于滚动视图 但事实并非如此 它的高度就像wrap content这意味着如果 EditText 中没有文本 我必须将光标指向要弹出的软键盘的第一 行 我想要的是我可
  • Android apk 调试模式工作正常,但发布模式给出太多警告

    我正在尝试从 eclipse 获取签名的 APK 我有一个可调试的 apk 版本 运行良好 现在发布时 当我尝试使用 Eclipse ADT 进行编译和签名时 我收到很多警告 其中大部分是can t find superclass or i
  • 协程和 Firebase:如何实现类似 Javascript 的 Promise.all()

    在 Javascript 中 您可以同时启动两个 或更多 异步任务 等待它们完成 然后执行某些操作 继续 const firstReturn secondReturn await Promise all firstPromise secon
  • 从 AlertDialog 返回值

    我想构建一个函数来创建 AlertDialog 并返回用户输入的字符串 这是我用于创建对话框的函数 如何返回该值 String m Text private String openDialog String title AlertDialo
  • 在 Android SDK 中通过单击按钮更改背景颜色不起作用

    我有一个简单的程序 可以在单击按钮后更改背景颜色 但它不起作用 public class ChangeBackgroundActivity extends Activity Called when the activity is first
  • Android 视图和视图组

    在安卓中ViewGroup继承自View A ViewGroup是一个容器 里面装有Views ViewGroup LinearLayout View TextView 为什么 Android 的人们将这种关系定义为Inheritance而
  • Exif 方向标签返回 0

    我正在开发一个自定义相机应用程序 我面临以下问题 当我尝试使用检索方向时ExifInterface 它总是返回 0 ORIENTATION UNDEFINED 这使我无法将图像旋转到正确的状态 从而无法正确显示 我使用示例代码来设置相机旋转
  • SambaFileInputStream 和 FileInputStream 有什么不同?

    我需要从 samba 服务器流式传输视频 并且我使用 nanohttpd 在我的项目中创建简单的服务器 当我使用本地文件中的 fileinputstream 时 视频视图可以按设置播放视频 http localhost 8080 publi
  • android-如何在谷歌地图上将标记的位置显示为地址

    我已经尝试过 commonsware googlemapsv2 教程 特别是在地图上拖动标记 但现在另一个问题困扰着我 问题是如何将标记的当前位置显示为地图下方或上方的地址 字符串 这是我使用的代码 public class MainAct
  • Android 中带有无尽列表视图滚动的 AsyncTask

    我正在创建一个应用程序 其中我需要有无限的滚动列表视图 我不想在我的应用程序中使用任何库 我在网上看到了一些有助于实现此类列表视图的示例 但我的疑问是 当我的数据来自服务器并在异步任务中进行解析时 如何才能拥有无尽的列表视图 如何从滚动异步

随机推荐

  • 第六章 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等