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

Android P Media源码笔记

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

北京

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

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

看不清楚,换张图片

免费获取短信验证码

Android P Media源码笔记

以前跟Android Meida部分源码,做了细致的笔记,贴出来说不定会有帮助呢。

MediaSession使用参考这篇文章:MediaSession框架全解析

跟踪的工程路径:\android_9\aosp\packages\apps\Car\Media
需要加载的目录:
\android_9\aosp\packages\apps
\android_9\aosp\frameworks\base

一、整体结构 —> 具体实现服务

说明多媒体跟踪代码的部分思路,直至找到具体的服务实现

1. 在MediaManager的setMediaClientComponent(…)中,新建了一个MediaBrowser,猜测是与服务通讯的桥梁
\android_9\aosp\packages\apps\Car\Media\class="lazy" data-src\com\android\car\media\MediaManager.java

    mBrowser = new MediaBrowser(mContext, component, mMediaBrowserConnectionCallback, null);
    mBrowser.connect();

->2. 跟踪MediaBrowser的connect()方法,确实是在启动服务并建立连接
X:\android_9\aosp\frameworks\base\media\java\android\media\browse\MediaBrowser.java

    final Intent intent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
    intent.setComponent(mServiceComponent);
    mServiceConnection = new MediaServiceConnection();

在MediaServiceConnection()中,新建了一个ServiceCallbacks(存有弱引用MediaBrowser实例)调用AIDL方法的Connect()将其传入

     mServiceBinder.connect(mContext.getPackageName(), mRootHints,
                            mServiceCallbacks);

->3. 跟踪mServiceComponent对象,弄清楚连接到哪里的服务

->1). mServiceComponent是一个ComponentName实例,ComponentName存有包名和类名,可以用作Intent的界面跳转
->2). mServiceComponent是在MediaBrowser构造方法中传入;
->3). 回到 1.MediaManager的setMediaClientComponent(…) 方法中,MediaBrowser的mServiceComponent对象是此方法参数传入的

->4. 在MediaActivity中,在changeMediaSource(…)方法中进行了setMediaClientComponent(…)的调用
X:\android_9\aosp\packages\apps\Car\Media\class="lazy" data-src\com\android\car\media\MediaActivity.java

    ComponentName component = mMediaSource.getBrowseServiceComponentName();
    MediaManager.getInstance(this).setMediaClientComponent(component);

->5. 跟踪MediaSource的getBrowseServiceComponentName()方法
X:\android_9\aosp\packages\apps\Car\libs\car-media-common\class="lazy" data-src\com\android\car\media\common\MediaSource.java

    public ComponentName getBrowseServiceComponentName() {
        if (mBrowseServiceClassName != null) {
            return new ComponentName(mPackageName, mBrowseServiceClassName);
        } else {
            return null;
        }
    }
    ...
    //mBrowseServiceClassName来自于此方法
    private String getBrowseServiceClassName(String packageName) {
        PackageManager packageManager = mContext.getPackageManager();
        Intent intent = new Intent();
        intent.setAction(MediaBrowserService.SERVICE_INTERFACE);
        intent.setPackage(packageName);
        List resolveInfos = packageManager.queryIntentServices(intent,
                PackageManager.GET_RESOLVED_FILTER);
        if (resolveInfos == null || resolveInfos.isEmpty()) {
            return null;
        }
        return resolveInfos.get(0).serviceInfo.name;
    }

->6. 找到MediaBrowserService.SERVICE_INTERFACE字段
X:\android_9\aosp\frameworks\base\media\java\android\service\media\MediaBrowserService.java

    @SdkConstant(SdkConstantType.SERVICE_ACTION)
    public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";

->7. 全局搜索

android.media.browse.MediaBrowserService
字段,在manifest找到对应的Action,找到具体实现服务
也就是以下路径:

蓝牙:
X:\android_9\aosp\packages\apps\Bluetooth\class="lazy" data-src\com\android\bluetooth\a2dpsink\mbs\A2dpMediaBrowserService.java
音乐:
X:\android_9\aosp\packages\apps\Music\class="lazy" data-src\com\android\music\MediaPlaybackService.java
X:\android_9\aosp\packages\apps\Car\LocalMediaPlayer\class="lazy" data-src\com\android\car\media\localmediaplayer\LocalMediaBrowserService.java
收音机:
X:\android_9\aosp\packages\apps\Car\Radio\class="lazy" data-src\com\android\car\radio\RadioService.java
二、结构与编写思路

Media的结构与编写思路

在这里插入图片描述
框图中的文件路径:

媒体界面
X:\android_9\aosp\packages\apps\Car\Media\class="lazy" data-src\com\android\car\media\MediaActivity.java
MediaSource
X:\android_9\aosp\packages\apps\Car\libs\car-media-common\class="lazy" data-src\com\android\car\media\common\MediaSource.java
MediaManager
X:\android_9\aosp\packages\apps\Car\Media\class="lazy" data-src\com\android\car\media\MediaManager.java
PlaybackModel
X:\android_9\aosp\packages\apps\Car\libs\car-media-common\class="lazy" data-src\com\android\car\media\common\PlaybackModel.java
三、MediaMession框架

对MediaMession框架的刷新与补充

总结
Mession底层仍是使用AIDL,它的优点和缺点同样突出
优点:完全统一了调用接口和回调接口,界面与不同类型音乐服务可以完全解耦、切换。
缺点:回调接口固定,限制较多,外部应用调用麻烦(需要完全理解框架并编写客户端);

1.MediaMession框架说明

(1)建立连接

客户端
第一步,创建mediaBrowser,绑定服务,并关联绑定回调

MediaBrowserCompat mediaBrowser = new MediaBrowserCompat(this,
            new ComponentName(this, MusicService.class), //绑定浏览器服务
            mConnectionCallback,//关联连接回调
            null);

第二步,获取mediaController,一般是在连接成功的回调中。当然源码在MediaSource中选择暴露获取controller的方法

//一般在连接回调获得
MediaBrowserCompat.ConnectionCallback mConnectionCallback =
new MediaBrowserCompat.ConnectionCallback() {
    @Override
    public void onConnected() {
        MediaSessionCompat.Token token = mMediaBrowser.getSessionToken();
        MediaControllerCompat mediaController = new MediaControllerCompat(this, token);
        //注册服务的回调
        mediaController.registerCallback(mMediaControllerCallback);
    }
};  
//MediaSource中
public MediaController getMediaController() {
    if (mBrowser == null || !mBrowser.isConnected()) {
        return null;
    }
    MediaSession.Token token = mBrowser.getSessionToken();
    return new MediaController(mContext, token);
}

本质是一样的,其中token相当于钥匙。然后就可以用mediaController来进行对服务的控制了,而

MediaController.CallBack
就是服务的回调

服务端
第一步,继承MediaBrowserService,重写

onGetRoot(..)
onLoadChildren(..)
方法,前者是判断是否允许客户端连接,后者是客户端异步请求信息的调用。

@Override
public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid,
                             Bundle rootHints) {
    
    if(!PackageUtil.isCallerAllowed(this, clientPackageName, clientUid)) {
        return new BrowserRoot(null, null);
    }
    //此方法只在服务连接的时候调用
    //返回一个rootId不为空的BrowserRoot则表示客户端可以连接服务,也可以浏览其媒体资源
    //如果返回null则表示客户端不能流量媒体资源
    return new BrowserRoot(BrowserRootId.MEDIA_ID_ROOT, null);
}
//需重写,异步请求数据,需要返回的结果
@Override
public void onLoadChildren(@NonNull String parentId, @NonNull Result<List> result) {
    ....
}

第二步,初始化服务端对象Session

//初始化
MediaSessionCompat mSession = new MediaSessionCompat(this, "MusicService");
//表示MediaBrowser与MediaBrowserService连接成功
setSessionToken(mSession.getSessionToken());
//设置控制监听
mSession.setCallback(SessionCallback);
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);

这里seesionCallBack就是

MediaSessionCompat.Callback
对象,客户端controller的控制操作,就会调用到这个对象的重写方法里。而session就是传给客户端信息的主要对象了。

(2)控制与回调
客户端控制:可以使用

mediaController.getTransportControls().skipToNext()
此类的方法,给服务通讯,而服务就会调到创建的MediaSessionCompat.Callback里

private android.support.v4.media.session.MediaSessionCompat.Callback SessionCallback = new MediaSessionCompat.Callback(){
    
    @Override
    public void onPlay() {
            ....
        }
    
    @Override
    public void onPause() {
            ....
        }
        .....
    }

此外,源码中显示mediaController可以获取当前播放媒体信息,播放状态等等,大致方法:

public MediaMetadata getMetadata() {...}
public PlaybackState getPlaybackState() {...}
public List getQueue(){...}
public CharSequence getQueueTitle(){....}

服务端回调:可以调用

session.setMetadata(MediaMetadata)
此类方法给客户端通讯,而客户端就会调到创建的
MediaController.CallBack

//媒体控制器控制播放过程中的回调接口
MediaControllerCompat.Callback mMediaControllerCallback =
   new MediaControllerCompat.Callback() {
        @Override
        public void onPlaybackStateChanged(@NonNull PlaybackStateCompat state) {
            //响应session.setPlaybackState(PlaybackState)
            //播放状态发生改变时的回调
            onMediaPlayStateChanged(state);
        }
        @Override
        public void onMetadataChanged(MediaMetadataCompat metadata) {
            //响应session.setMetadata(MediaMetadata)
            //播放的媒体数据发生变化时的回调
            if(metadata == null) {
                return;
            }
            onPlayMetadataChanged(metadata);
        }
    };

(3)异步主动获取信息
客户端:mediaBrowser发起信息请求,注意发起前需要先unsubscribe,好像是官方bug。这里传入的ID,在服务端进行对请求的区分。

//----------异步获取数据------
//订阅/发起信息请求
mediaBrowser.unsubscribe(BrowserRootId.MEDIA_ID_MUSIC_LIST_REFRESH);
mediaBrowser.subscribe(BrowserRootId.MEDIA_ID_MUSIC_LIST_REFRESH, mSubscriptionCallback);
//异步回调接口
MediaBrowserCompat.SubscriptionCallback mSubscriptionCallback =
    new MediaBrowserCompat.SubscriptionCallback() {
        @Override
        public void onChildrenLoaded(@NonNull String parentId,
                                     @NonNull List children) {
            //数据获取成功后的回调
        }
        @Override
        public void onError(@NonNull String id) {
            //数据获取失败的回调
        }
    };

这部分源码很复杂,主要是在MediaSource.java中,简单说明对外调用的方法是

subscribeChildren(...)
,而subscription对象继承于
MediaBrowser.SubscriptionCallback
,回调也在其中重写方法里。

public void subscribeChildren(@Nullable String parentId, ItemsSubscription callback) {
    if (mBrowser == null) {
        throw new IllegalStateException("Browsing is not available for this source: "
                + getName());
    }
    if (mRootNode == null && !mBrowser.isConnected()) {
        throw new IllegalStateException("Subscribing to the root node can only be done while "
                + "connected: " + getName());
    }
    mRootNode = mBrowser.getRoot();
    String itemId = parentId != null ? parentId : mRootNode;
    ChildrenSubscription subscription = mChildrenSubscriptions.get(itemId);
    if (subscription != null) {
        subscription.add(callback);
    } else {
        subscription = new ChildrenSubscription(mBrowser, itemId);
        subscription.add(callback);
        mChildrenSubscriptions.put(itemId, subscription);
        subscription.start(CHILDREN_SUBSCRIPTION_RETRIES,
                CHILDREN_SUBSCRIPTION_RETRY_TIME_MS);
    }
}

服务端:继承MediaBrowserService时就必须重写

onLoadChildren(..)
方法
parentId
可用于区分请求,
result.sendResult(mediaItems)
用作向客户端返回一个
list
,不管如何操作前需要
result.detach();

@Override
public void onLoadChildren(@NonNull String parentId, @NonNull Result<List> result) {
    if(BrowserRootId.MEDIA_ID_MUSIC_LIST_REFRESH.equals(parentId)) {
        //一定要先detach()
        result.detach();
        //模拟获取数据的过程
        MediaMetadataCompat metadata = new MediaMetadataCompat.Builder()
                .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, ""+R.raw.jinglebells)
                .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "圣诞歌")
                .build();
        ArrayList mediaItems = new ArrayList();
        mediaItems.add(createMediaItem(metadata));
        //向Browser发送数据,返回的是一个List
        result.sendResult(mediaItems);
    } else {
        result.detach();
    }
}

这部分服务代码就很多了,根据parentId进行区分再读取数据返回

(4)QueueItem和MediaMetadata

session.setQueue(List)
用于回调播放列表。
List
用于异步请求返回。
QueueItem和MediaMetadata是什么关系呢?QueueItem在构造的时候,需要MediaDescription,而MediaDescription可以通过MediaMetadata获得。在构造QueueItem时,注意id不重复

在DataModel.java中找到栗子:

MediaDescription.Builder builder = new MediaDescription.Builder()
    .setMediaId(cursor.getString(keyColumn))
    .setTitle(cursor.getString(titleColumn))
    .setExtras(path);
if (subtitleColumn != -1) {
    builder.setSubtitle(cursor.getString(subtitleColumn));
}
MediaDescription description = builder.build();
results.add(new MediaItem(description, mFlags));
// We rebuild the queue here so if the user selects the item then we
// can immediately use this queue.
if (mQueue != null) {
    mQueue.add(new QueueItem(description, idx));
}
idx++;

以上是读取数据时,创建QueueItem和MediaItem区别,首先都是有description作为参数,区别在于MediaItem有一个mFlags标志位,而mQueue的参数idx是唯一不重复的id。

2.类与方法的说明 (1)主要类与概念
概念
android.media.session.MediaSession 受控端
android.media.session.MediaSession.Token 配对密钥
android.media.session.MediaController 控制端
android.media.session.MediaSession.Callback 受控端回调,可以接受到控制端的指令
android.media.session.MediaController.TransportControls 控制端的遥控器,用于发送指令
android.media.session.MediaController.Callback 控制端回调,可以接受到受控端的状态
(2)客户端调用服务端
意义 TransportControls MediaSession.Callback 说明
播放 play() onPlay()
停止 stop() onStop()
暂停 pause() onPause()
指定播放位置 seekTo(long pos) onSeekTo(long)
快进 fastForward() onFastForward()
回倒 rewind() onRewind()
下一首 skipToNext() onSkipToNext()
上一首 skipToPrevious() onSkipToPrevious()
指定id播放 skipToQueueItem(long) onSkipToQueueItem(long) 指定的是Queue的id
指定id播放 playFromMediaId(String,Bundle) onPlayFromMediaId(String,Bundle) 指定的是MediaMetadata的id
搜索播放 playFromSearch(String,Bundle) onPlayFromSearch(String,Bundle) 需求不常见
指定uri播放 playFromUri(Uri,Bundle) onPlayFromUri(Uri,Bundle) 需求不常见
发送自定义动作 sendCustomAction(String,Bundle) onCustomAction(String,Bundle) 可用来更换播放模式、重新加载音乐列表等
打分 setRating(Rating rating) onSetRating(Rating) 内置的评分系统有星级、红心、赞/踩、百分比
(3)服务端回调给客户端
意义 MediaSession MediaController.Callback 说明
当前播放音乐 setMetadata(MediaMetadata) onMetadataChanged(MediaMetadata)
播放状态 setPlaybackState(PlaybackState) onPlaybackStateChanged(PlaybackState)
播放队列 setQueue(List MediaSession.QueueItem>) onQueueChanged(List MediaSession.QueueItem>)
播放队列标题 setQueueTitle(CharSequence) onQueueTitleChanged(CharSequence) 不常用
额外信息 setExtras(Bundle) onExtrasChanged(Bundle) 可以记录播放模式
自定义事件 sendSessionEvent(String,Bundle) onSessionEvent(String, Bundle)

作者:qzns木雨


免责声明:

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

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

Android P Media源码笔记

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

下载Word文档

猜你喜欢

Android P Media源码笔记

以前跟Android Meida部分源码,做了细致的笔记,贴出来说不定会有帮助呢。 MediaSession使用参考这篇文章:MediaSession框架全解析跟踪的工程路径:\android_9\aosp\packages\apps\Ca
2022-06-06

Android P Audio系统笔记:AudioPolicy&amp;AudioFlinger初始化

AudioPolicy&AudioFlinger初始化总体框架初始化步骤简介初始化步骤详细流程分析1、loadConfig()2、initialize()2.1、初始音频路由引擎2.2、加载so 并且打开设备节点2.3、打开输出流 总体框架
2022-06-06

Android笔记之:CM9源码下载与编译的应用

公司最近也开始基于android4.0 ICS修改框架了,公司的手机暂时不适合拿回家测试,也没有kernel的权限。从个人的角度看,我手上现在有两部手机,一部是Htc G9 Aria,一部是Samsung I9100 Galaxys2,Cy
2022-06-06

ClickHouse源码笔记5:聚合函数的源码再梳理

笔者在源码笔记1之中分析过ClickHouse的聚合函数的实现,但是对于各个接口函数的实际如何共同工作的源码,回头看并没有那么明晰,主要原因是没有结合Aggregator的类来一起分析聚合函数的是如果工作起来的。所以决定重新再完成一篇聚合函数的源码梳理的文章,
ClickHouse源码笔记5:聚合函数的源码再梳理
2021-04-02

Android手势密码view学习笔记(二)

我们还是接着我们上一篇博客中的内容往下讲哈,上一节 Android手势密码view笔记(一)我们已经实现了我们的IndicatorView指示器view了:下面我们来实现下我们的手势密码view:实现思路: 1、我们照样需要拿到用户需要显示
2022-06-06

Android手势密码view学习笔记(一)

刚接触Android的时候看到别人写的手势密码view,然后当时就在想,我什么时候才能写出如此高端的东西?? 没关系,不要怕哈,说出这样话的人不是你技术不咋地而是你不愿意花时间去研究它,其实也没有那么难哦(世上无难事,只怕有心人!),下面我
2022-06-06

ClickHouse源码笔记2:聚合流程的实现

上篇笔记讲到了聚合函数的实现并且带大家看了聚合函数是如何注册到ClickHouse之中的并被调用使用的。这篇笔记,笔者会续上上篇的内容,将剖析一把ClickHouse聚合流程的整体实现。第二篇文章,我们来一起看看聚合流程的实现~~ 上车!1.基础知识的梳理Cl
ClickHouse源码笔记2:聚合流程的实现
2016-07-29

ClickHouse源码笔记1:聚合函数的实现

由于工作的需求,后续笔者工作需要和开源的OLAP数据库ClickHouse打交道。ClickHouse是Yandex在2016年6月15日开源了一个分析型数据库,以强悍的单机处理能力被称道。笔者在实际测试ClickHouse和阅读ClickHouse的源码过程
ClickHouse源码笔记1:聚合函数的实现
2018-06-25

第一行代码—Android第二版学习笔记

Android 第一行代码学习笔记第一章 概述1.1安卓系统架构1.2Android四大组件1.3项目结构1.4app目录结构1.5 项目运行原理1.6 res目录详解1.7日志工具的使用第二章 活动2.1 创建基本活动2.2 使用Inte
2022-06-06

ClickHouse源码笔记6:探究列式存储系统的排序

分析完成了聚合以及向量化过滤,向量化的函数计算之后。本篇,笔者将分析数据库的一个重要算子:排序。让我们从源码的角度来剖析ClickHouse作为列式存储系统是如何实现排序的。本系列文章的源码分析基于ClickHouse v19.16.2.2的版本。1.执行计划
ClickHouse源码笔记6:探究列式存储系统的排序
2017-04-13

Android学习笔记--Activity中使用Intent传值示例代码

Intent,又称为意图,是一种运行时绑定机制,它能在程序运行的过程中链接两个不同的组件(Activity、Service、BroadcastReceiver)。通过Intent,程序可以向Android表达某种请求或意愿,Android会
2022-06-06

Android学习笔记--通过Application传递数据代码示例

在整个Android程序中,有时需要保存某些全局的数据(如:用户信息),方便在程序的任何地方调用。在Activity之间数据传递中有一种比较使用的方式,就是全局对象,使用过J2EE的都应该知道JavaWeb的四个作用域,其中Applicat
2022-06-06

Android开发自学笔记(五):使用代码控制界面

酷酷的外表已经具备了,那就开始让我们真正把它的功能给实现起来吧,外强中干,花拳绣腿可不行哦,我们需要真正的本领,需要一颗自强不息的心哦,常常想想自己的梦想什么,这样才不会迷失自己,才会在茫茫的世界中找到自己前进的方向!我不会告诉你我刚看过《
2022-06-06

记录mac端下载android源码过程中的错误

本文主要记录下载过程中的错误,详细的下载过程后续再补上,折腾这些错误折腾了很久,在此记录一下 repo: command not found终端直接输入:echo 'export PATH=$PATH:$HOME/bin' >> ~/.ba
2022-06-06

Android学习笔记--使用剪切板在Activity中传值示例代码

在Activity之间传递数据还可以利用一些技巧,不管windows还是Linux操作系统,都会支持一种叫剪切板的技术,也就是某一个程序将一些数据复制到剪切板上,然后其他的任何程序都可以从剪切板中获取数据,在Android系统中也存在此技术
2022-06-06

软件测试源码、框架、笔记学习教程:最新接口测试技术精讲

为什么要做接口测试?有两个原因:尽早暴露出接口问题,减少返工工作量和自动化接口测试,减少冒烟、回归测试的重复工作。但是如何在测试工作中尽可能做好接口测试呢?接口测试的用例设计是关键,不能只是单纯正常请求通过就算接口测试过了,要从业务、功能、
2023-06-04

编程热搜

  • 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第一次实验

目录