我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android匿名内存深入分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android匿名内存深入分析

Android 匿名内存解析

有了binder机制为什么还需要匿名内存来实现IPC呢?我觉得很大的原因就是binder传输是有大小限制的,不说应用层的限制。在驱动中binder的传输大小被限制在了4M,分享一张图片可能就超过了这个限制。匿名内存的主要解决思路就是通过binder传输文件描述符,使得两个进程都能访问同一个地址来实现共享。

MemoryFile使用

在平常开发中android提供了MemoryFile来实现匿名内存。看下最简单的实现。

Service端

​
const val GET_ASH_MEMORY = 1000
class MyService : Service() {
    val ashData = "AshDemo".toByteArray()
    override fun onBind(intent: Intent): IBinder {
        return object : Binder() {
            override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
                when(code){
                    GET_ASH_MEMORY->{//收到客户端请求的时候会烦
                        val descriptor = createMemoryFile()
                        reply?.writeParcelable(descriptor, 0)
                        reply?.writeInt(ashData.size)
                        return true
                    }
                    else->{
                        return super.onTransact(code, data, reply, flags)
                    }
                }
            }
        }
    }
    private fun createMemoryFile(): ParcelFileDescriptor? {
        val file = MemoryFile("AshFile", 1024)//创建MemoryFile
        val descriptorMethod = file.javaClass.getDeclaredMethod("getFileDescriptor")
        val fd=descriptorMethod.invoke(file)//反射拿到fd
        file.writeBytes(ashData, 0, 0,ashData.size)//写入字符串
        return ParcelFileDescriptor.dup(fd as FileDescriptor?)//返回一个封装的fd
    }
}

Server的功能很简单收到GET_ASH_MEMORY请求的时候创建一个MemoryFile,往里写入一个字符串的byte数组,然后将fd和字符长度写入reply中返回给客户端。

Client端

​
class MainActivity : AppCompatActivity() {
    val connect = object :ServiceConnection{
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            val reply = Parcel.obtain()
            val sendData = Parcel.obtain()
            service?.transact(GET_ASH_MEMORY, sendData, reply, 0)//传输信号GET_ASH_MEMORY
            val pfd = reply.readParcelable<ParcelFileDescriptor>(javaClass.classLoader)
            val descriptor = pfd?.fileDescriptor//拿到fd
            val size = reply.readInt()//拿到长度
            val input = FileInputStream(descriptor)
            val bytes = input.readBytes()
            val message = String(bytes, 0, size, Charsets.UTF_8)//生成string
            Toast.makeText(this@MainActivity,message,Toast.LENGTH_SHORT).show()
        }
​
        override fun onServiceDisconnected(name: ComponentName?) {
        }
​
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<TextView>(R.id.intent).setOnClickListener {
          //启动服务
            bindService(Intent(this,MyService::class.java),connect, Context.BIND_AUTO_CREATE)
        }
    }
}

客户端也很简单,启动服务,发送一个获取MemoryFile的请求,然后通过reply拿到fd和长度,用FileInputStream读取fd中的内容,最后通过toast可以验证这个message已经拿到了。

AshMemory 创建原理

    public MemoryFile(String name, int length) throws IOException {
        try {
            mSharedMemory = SharedMemory.create(name, length);
            mMapping = mSharedMemory.mapReadWrite();
        } catch (ErrnoException ex) {
            ex.rethrowAsIOException();
        }
    }

MemoryFile就是对SharedMemory的一层封装,具体的工能都是SharedMemory实现的。看SharedMemory的实现。

    public static @NonNull SharedMemory create(@Nullable String name, int size)
            throws ErrnoException {
        if (size <= 0) {
            throw new IllegalArgumentException("Size must be greater than zero");
        }
        return new SharedMemory(nCreate(name, size));
    }
  private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;

通过一个JNI获得fd,从这里可以推断出java层也只是一个封装,拿到的已经是创建好的fd。

//frameworks/base/core/jni/android_os_SharedMemory.cpp
jobject SharedMemory_nCreate(JNIEnv* env, jobject, jstring jname, jint size) {
    const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr;
    int fd = ashmem_create_region(name, size);//创建匿名内存块
    int err = fd < 0 ? errno : 0;
    if (name) {
        env->ReleaseStringUTFChars(jname, name);
    }
    if (fd < 0) {
        jniThrowErrnoException(env, "SharedMemory_create", err);
        return nullptr;
    }
    jobject jifd = jniCreateFileDescriptor(env, fd);//创建java fd返回
    if (jifd == nullptr) {
        close(fd);
    }
    return jifd;
}

通过cutils中的ashmem_create_region函数实现的创建

//system/core/libcutils/ashmem-dev.cpp
int ashmem_create_region(const char *name, size_t size)
{
    int ret, save_errno;
​
    if (has_memfd_support()) {//老版本兼容用
        return memfd_create_region(name ? name : "none", size);
    }
​
    int fd = __ashmem_open();//打开Ashmem驱动
    if (fd < 0) {
        return fd;
    }
    if (name) {
        char buf[ASHMEM_NAME_LEN] = {0};
        strlcpy(buf, name, sizeof(buf));
        ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf));//通过ioctl设置名字
        if (ret < 0) {
            goto error;
        }
    }
    ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));//通过ioctl设置大小
    if (ret < 0) {
        goto error;
    }
    return fd;
error:
    save_errno = errno;
    close(fd);
    errno = save_errno;
    return ret;
}
​

标准的驱动交互操作

1.open打开驱动

2.通过ioctl与驱动进行交互

下面看下open的流程

static int __ashmem_open()
{
    int fd;
​
    pthread_mutex_lock(&__ashmem_lock);
    fd = __ashmem_open_locked();
    pthread_mutex_unlock(&__ashmem_lock);
​
    return fd;
}
​

static int __ashmem_open_locked()
{
    static const std::string ashmem_device_path = get_ashmem_device_path();//拿到Ashmem驱动路径
    if (ashmem_device_path.empty()) {
        return -1;
    }
    int fd = TEMP_FAILURE_RETRY(open(ashmem_device_path.c_str(), O_RDWR | O_CLOEXEC));
    return fd;
}

回到MemoryFile的构造函数中,拿到了驱动的fd之后调用了mapReadWrite

    public @NonNull ByteBuffer mapReadWrite() throws ErrnoException {
        return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize);
    }
 public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {
        checkOpen();
        validateProt(prot);
        if (offset < 0) {
            throw new IllegalArgumentException("Offset must be >= 0");
        }
        if (length <= 0) {
            throw new IllegalArgumentException("Length must be > 0");
        }
        if (offset + length > mSize) {
            throw new IllegalArgumentException("offset + length must not exceed getSize()");
        }
        long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);//调用了系统的mmap
        boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0;
        Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire());
        return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);
    }
​

到这里就有一个疑问,Linux就有共享内存,android为什么要自己搞一套,只能看下Ashmemory驱动的实现了。

驱动第一步看init和file_operations

static int __init ashmem_init(void)
{
    int ret = -ENOMEM;
​
    ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
                           sizeof(struct ashmem_area),
                           0, 0, NULL);//创建
    if (!ashmem_area_cachep) {
        pr_err("failed to create slab cache\n");
        goto out;
    }
​
    ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",
                        sizeof(struct ashmem_range),
                        0, SLAB_RECLAIM_ACCOUNT, NULL);//创建
    if (!ashmem_range_cachep) {
        pr_err("failed to create slab cache\n");
        goto out_free1;
    }
​
    ret = misc_register(&ashmem_misc);//注册为了一个misc设备
    ........
    return ret;
}

创建了两个内存分配器ashmem_area_cachep和ashmem_range_cachep用于分配ashmem_area和ashmem_range

//common/drivers/staging/android/ashmem.c
static const struct file_operations ashmem_fops = {
    .owner = THIS_MODULE,
    .open = ashmem_open,
    .release = ashmem_release,
    .read_iter = ashmem_read_iter,
    .llseek = ashmem_llseek,
    .mmap = ashmem_mmap,
    .unlocked_ioctl = ashmem_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = compat_ashmem_ioctl,
#endif
#ifdef CONFIG_PROC_FS
    .show_fdinfo = ashmem_show_fdinfo,
#endif
};
​

open调用的就是ashmem_open

static int ashmem_open(struct inode *inode, struct file *file)
{
    struct ashmem_area *asma;
    int ret;
​
    ret = generic_file_open(inode, file);
    if (ret)
        return ret;
​
    asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);//分配一个ashmem_area
    if (!asma)
        return -ENOMEM;
​
    INIT_LIST_HEAD(&asma->unpinned_list);//初始化unpinned_list
    memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);//初始化一个名字
    asma->prot_mask = PROT_MASK;
    file->private_data = asma;
    return 0;
}

ioctl设置名字和长度调用的就是ashmem_ioctl

static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct ashmem_area *asma = file->private_data;
    long ret = -ENOTTY;
​
    switch (cmd) {
    case ASHMEM_SET_NAME:
        ret = set_name(asma, (void __user *)arg);
        break;
    case ASHMEM_SET_SIZE:
        ret = -EINVAL;
        mutex_lock(&ashmem_mutex);
        if (!asma->file) {
            ret = 0;
            asma->size = (size_t)arg;
        }
        mutex_unlock(&ashmem_mutex);
        break;
    }
  ........
  }

实现也都很简单就是改变了一下asma里的值。接下来就是重点mmap了,具体是怎么分配内存的。

​static int ashmem_mmap(struct file *file, struct vm_area_struct *vma)
{
    static struct file_operations vmfile_fops;
    struct ashmem_area *asma = file->private_data;
    int ret = 0;
​
    mutex_lock(&ashmem_mutex);
​
    
    if (!asma->size) {//判断设置了size
        ret = -EINVAL;
        goto out;
    }
​
    
    if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {//判断大小是否超过了虚拟内存
        ret = -EINVAL;
        goto out;
    }
​
    
    if ((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) &
        calc_vm_prot_bits(PROT_MASK, 0)) {//权限判断
        ret = -EPERM;
        goto out;
    }
    vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);
​
    if (!asma->file) {//是否创建过临时文件,没创建过进入
        char *name = ASHMEM_NAME_DEF;
        struct file *vmfile;
        struct inode *inode;
​
        if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0')
            name = asma->name;
​
        
        vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);//调用linux函数在tmpfs中创建临时文件
        if (IS_ERR(vmfile)) {
            ret = PTR_ERR(vmfile);
            goto out;
        }
        vmfile->f_mode |= FMODE_LSEEK;
        inode = file_inode(vmfile);
        lockdep_set_class(&inode->i_rwsem, &backing_shmem_inode_class);
        asma->file = vmfile;
        
        if (!vmfile_fops.mmap) {//设置了临时文件的文件操作,防止有其他程序mmap这个临时文件
            vmfile_fops = *vmfile->f_op;
            vmfile_fops.mmap = ashmem_vmfile_mmap;
            vmfile_fops.get_unmapped_area =
                    ashmem_vmfile_get_unmapped_area;
        }
        vmfile->f_op = &vmfile_fops;
    }
    get_file(asma->file);
​
    
    if (vma->vm_flags & VM_SHARED) {//这块内存是不是需要跨进程
        ret = shmem_zero_setup(vma);//设置文件
        if (ret) {
            fput(asma->file);
            goto out;
        }
    } else {
    
        vma_set_anonymous(vma);
    }
​
    vma_set_file(vma, asma->file);
    
    fput(asma->file);
​
out:
    mutex_unlock(&ashmem_mutex);
    return ret;
}

函数很长,但是思路还是很清晰的。创建临时文件,设置文件操作。其中调用的都是linux的系统函数了,看真正设置的shmem_zero_setup函数

int shmem_zero_setup(struct vm_area_struct *vma)
{
    struct file *file;
    loff_t size = vma->vm_end - vma->vm_start;
​
    
    file = shmem_kernel_file_setup("dev/zero", size, vma->vm_flags);
    if (IS_ERR(file))
        return PTR_ERR(file);
​
    if (vma->vm_file)
        fput(vma->vm_file);
    vma->vm_file = file;
    vma->vm_ops = &shmem_vm_ops;//很重要的操作将这块虚拟内存的vm_ops设置为shmem_vm_ops
​
    if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE) &&
            ((vma->vm_start + ~HPAGE_PMD_MASK) & HPAGE_PMD_MASK) <
            (vma->vm_end & HPAGE_PMD_MASK)) {
        khugepaged_enter(vma, vma->vm_flags);
    }
​
    return 0;
}
static const struct vm_operations_struct shmem_vm_ops = {
    .fault      = shmem_fault,//Linux的共享内存实现的基础
    .map_pages  = filemap_map_pages,
#ifdef CONFIG_NUMA
    .set_policy     = shmem_set_policy,
    .get_policy     = shmem_get_policy,
#endif
};

到这里共享内存的初始化就结束了。

AshMemory 读写

​//frameworks/base/core/java/android/os/MemoryFile.java
public void writeBytes(byte[] buffer, int class="lazy" data-srcOffset, int destOffset, int count)
            throws IOException {
        beginAccess();
        try {
            mMapping.position(destOffset);
            mMapping.put(buffer, class="lazy" data-srcOffset, count);
        } finally {
            endAccess();
        }
    }
    private void beginAccess() throws IOException {
        checkActive();
        if (mAllowPurging) {
            if (native_pin(mSharedMemory.getFileDescriptor(), true)) {
                throw new IOException("MemoryFile has been purged");
            }
        }
    }
​
    private void endAccess() throws IOException {
        if (mAllowPurging) {
            native_pin(mSharedMemory.getFileDescriptor(), false);
        }
    }

其中beginAccess和endAccess是对应的。调用的都是native_pin是一个native函数,一个参数是true一个是false。pin的作用就是锁住这块内存不被系统回收,当不使用的时候就解锁。

static jboolean android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDescriptor,
        jboolean pin) {
    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    int result = (pin ? ashmem_pin_region(fd, 0, 0) : ashmem_unpin_region(fd, 0, 0));
    if (result < 0) {
        jniThrowException(env, "java/io/IOException", NULL);
    }
    return result == ASHMEM_WAS_PURGED;
}

调用的ashmem_pin_region和ashmem_unpin_region来实现解锁和解锁。实现还是在ashmem-dev.cpp

//system/core/libcutils/ashmem-dev.cpp
int ashmem_pin_region(int fd, size_t offset, size_t len)
{
    .......
    ashmem_pin pin = { static_cast<uint32_t>(offset), static_cast<uint32_t>(len) };
    return __ashmem_check_failure(fd, TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_PIN, &pin)));
}

通过的也是ioclt通知的驱动。加锁的细节就不展开了。具体的写入就是利用linux的共享内存机制实现的共享。

Linux共享机制简介

共享简单的实现方式就是通过mmap同一个文件来实现。但是真实文件的读写速度实在是太慢了,所以利用tmpfs这个虚拟文件系统,创建了一个虚拟文件来读写。同时这块虚拟内存在上面也写到重写了vm_ops。当有进程操作这个虚拟内存的时候会触发缺页错误,接着会去查找Page缓存,由于是第一次所以没有缓存,读取物理内存,同时加入Page缓存,当第二个进程进来的时也触发缺页错误时就能找到Page缓存了,那么他们操作的就是同一块物理内存了。

总结

看完之后发现AshMemory是基于Linux的共享内存实现的。做了几点改造

  • 首先把一整块内存变成了一个个region,这样在不用的时候可以解锁来让系统回收。
  • 将Linux共享内存的整数标记共享内存,而AshMemory是用的fd,让它可以利用binder机制的fd传输。
  • 读写设置都做了加锁的处理,减少了用户使用的难度。

以上就是Android 匿名内存深入分析的详细内容,更多关于Android 匿名内存的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android匿名内存深入分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android匿名内存深入分析

这篇文章主要为大家介绍了Android匿名内存深入分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-15

Android匿名内存怎么实现

本篇内容主要讲解“Android匿名内存怎么实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android匿名内存怎么实现”吧!Android 匿名内存解析有了binder机制为什么还需要匿名
2023-07-05

Redis内存碎片原理深入分析

这篇文章主要为大家介绍了Redis内存碎片原理深入分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-01

如何实现Android中图片占用内存的深入分析

小编今天带大家了解如何实现Android中图片占用内存的深入分析,文中知识点介绍的非常详细。觉得有帮助的朋友可以跟着小编一起浏览文章的内容,希望能够帮助更多想解决这个问题的朋友找到问题的答案,下面跟着小编一起深入学习“如何实现Android
2023-06-26

深入浅析Java中的内存分配机制

本篇文章给大家分享的是有关深入浅析Java中的内存分配机制,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java 内存分配深入理解Java程序运行在JVM(Java Virt
2023-05-31

【Android】CalledFromWrongThreadException 深入源码分析

先上结论 出现此问题的原因是:在非 UI 线程中创建了 Dialog,而在 UI 线程中调用了 show() 方法 问题还原 在使用 dialog 的时候,因为线程问题,在调用 dismiss() 方法的时候,出现如下常见的 crash–O
2022-06-06

C++深入分析数据在内存中的存储形态

使用编程语言进行编程时,需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着,当您创建一个变量时,就会在内存中保留一些空间。您可能需要存储各种数据类型的信息,操作系统会根据变量的数据类型,来分配内存和决定在保留内存中存储什么
2023-01-06

Android编程之匿名内部类与回调函数用法分析

本文实例讲述了Android编程之匿名内部类与回调函数用法。分享给大家供大家参考,具体如下: 我们Android开发中经常用到一些匿名内部类,及其其中的回调函数,例如,我们给Button设置监听器时候通常要实现OnCLickListener
2022-06-06

Android LayoutInflater深入分析及应用

LayoutInflater解析 前言: 在Android中,如果是初级玩家,很可能对LayoutInflater不太熟悉,或许只是在Fragment的onCreateView()中模式化的使用过而已。但如果稍微有些工作经验的人就知道,这个
2022-06-06

android内存及内存溢出分析详解

一、Android的内存机制 Android的程序由Java语言编写,所以Android的内存管理与Java的内存管理相似。程序员通过new为对象分配内存,所有对象在java堆内分配空间;然而对象的释放是由垃圾回收器来完成的。C/C+
2022-06-06

C语言数据在内存中的存储流程深入分析

使用编程语言进行编程时,需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着,当您创建一个变量时,就会在内存中保留一些空间。您可能需要存储各种数据类型的信息,操作系统会根据变量的数据类型,来分配内存和决定在保留内存中存储什么
2022-11-13

JVM内存管理深入垃圾收集器与内存分配策略的示例分析

这篇文章给大家介绍JVM内存管理深入垃圾收集器与内存分配策略的示例分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出
2023-06-17

C++虚函数表与类的内存分布深入分析理解

对C++了解的人都应该知道虚函数(VirtualFunction)是通过一张虚函数表(VirtualTable)来实现的。简称为V-Table。本文就将详细讲讲虚函数表的原理与使用,需要的可以参考一下
2022-11-13

深入分析Android NFC技术 android nfc开发

从概念,实现原理以及最红实现的源码等有助于大家对NFC技术有更深入的理解。NFC 是 Near Field Communication 缩写,即近距离无线通讯技术。可以在移动设备、消费类电子产品、PC 和智能控件工具间进行近距离无线通信。简
2023-05-30

android生命周期深入分析(一)

Android 系统在Activity 生命周期中加入一些钩子,我们可以在这些系统预留的钩子中做一些事情。 例举了 7 个常用的钩子: protected void onCreate(Bundle savedInstanceState) p
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录