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

详解Android壁纸服务的启动过程

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

北京

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

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

看不清楚,换张图片

免费获取短信验证码

详解Android壁纸服务的启动过程

壁纸基础

android中的壁纸分为动态壁纸和静态壁纸两种,两种类型的壁纸都以Service的类型运行在系统后台。

  • 静态壁纸:仅以图片的形式进行展示对于静态壁纸,可以使用WallpaperManager中的getDrawable()等接口获取到当前的bitmap图像。
  • 动态壁纸:显示的内容为动态的内容,同时可以对用户的操作做出响应对于动态壁纸的实时图像,是没办法通过android中原生的接口获取到,需要获取到动态壁纸的图像得自己修改源码。

壁纸实现时涉及的几个主要的类:

  • WallpaperService及其内部类Engine:壁纸在WallpaperService这个服务中运行,当需要实现自己的壁纸时,继承和实现这个类,是首先需要做的。Engine是WallpaperService中的一个内部类,实现了壁纸服务窗口的创建以及Surface的维护,同时Engine内部类还提供了onVisibilityChanged(),onCommand()等回调方法,用于可见状态变化和用户触摸事件等。Engine类因此也是壁纸实现的核心类,实现和重写其接口在开发中也相当重要。
  • WallpaperManagerService和WallpaperManager:WallpaperManagerService用于管理壁纸的运行与切换,并通过WallpaperManager对外界提供操作壁纸的接口。
  • WindowMangerService:该类用于计算壁纸窗口的Z序,可见性以及为壁纸窗口应用动画。

壁纸服务的两种启动场景

非首次重启壁纸服务启动流程

SystemService进程启动时,会启动各种系统服务。在该类的startOtherServices()方法中会首先拉起
WallpaperManagerService,通过该类,WallpaperService后面才得以启动。


            if (context.getResources().getBoolean(R.bool.config_enableWallpaperService)) {
                t.traceBegin("StartWallpaperManagerService");
                mSystemServiceManager.startService(WALLPAPER_SERVICE_CLASS);
                t.traceEnd();
            } else {
                Slog.i(TAG, "Wallpaper service disabled by config");
            }

WallpaperManagerService启动之后systemReady()方法中会通过loadSettingsLocked()方法加载用户设置过的壁纸信息,然后监听用户切换用户switchUser(),切换用户时,switchWallpaper()会调用bindWallpaperComponentLocked()方法拉起对应的壁纸服务。

手动切换时壁纸服务的启动流程

手动切换壁纸服务时需要通过WallpaperManager.getIWallpaperManager().setWallpaperComponent()方法完成,我们在这个接口中传入壁纸服务对应的ComponentName,getIWallpaperManager返回的是WallpaperManagerService的Bp(binder proxy binder代理)端,在WallpaperManagerService端,我们可以查看到setWallpaperComponent的具体实现,


private void setWallpaperComponent(ComponentName name, int userId) {
        ...
        
        checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);

        int which = FLAG_SYSTEM;
        boolean shouldNotifyColors = false;
        WallpaperData wallpaper;

        synchronized (mLock) {
            Slog.v(TAG, "setWallpaperComponent name=" + name);
            
            wallpaper = mWallpaperMap.get(userId);
            if (wallpaper == null) {
                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
            }
      		 ...
      		    // 在这里真正会去拉起对应的WallPaperService
                if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) {
              ...
    }

setWallpaperComponent最终也是通过bindWallpaperComponentLocked拉起壁纸服务

壁纸服务启动过程

1.校验是否是壁纸服务

bindWallpaperComponentLocked()方法将会启动该ComponentName所指定的WallpaperService,在启动的时候首先会进行校验,以确定待拉起的服务是一个壁纸服务,


    private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
      	...
            int serviceUserId = wallpaper.userId;
            ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
                    PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId);
            if (si == null) {
                // The wallpaper component we're trying to use doesn't exist
                Slog.w(TAG, "Attempted wallpaper " + componentName + " is unavailable");
                return false;
            }
            
            if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
                String msg = "Selected service does not have "
                        + android.Manifest.permission.BIND_WALLPAPER
                        + ": " + componentName;
                if (fromUser) {
                    throw new SecurityException(msg);
                }
                Slog.w(TAG, msg);
                return false;
            }

            WallpaperInfo wi = null;

            Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
            if (componentName != null && !componentName.equals(mImageWallpaper)) {
                // Make sure the selected service is actually a wallpaper service.
                			
                List<ResolveInfo> ris =
                        mIPackageManager.queryIntentServices(intent,
                                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
                                PackageManager.GET_META_DATA, serviceUserId).getList();
                for (int i=0; i<ris.size(); i++) {
                    ServiceInfo rsi = ris.get(i).serviceInfo;
                    if (rsi.name.equals(si.name) &&
                            rsi.packageName.equals(si.packageName)) {
                        try {
                        
                            wi = new WallpaperInfo(mContext, ris.get(i));
                        } catch (XmlPullParserException e) {
                            if (fromUser) {
                                throw new IllegalArgumentException(e);
                            }
                            Slog.w(TAG, e);
                            return false;
                        } catch (IOException e) {
                            if (fromUser) {
                                throw new IllegalArgumentException(e);
                            }
                            Slog.w(TAG, e);
                            return false;
                        }
                        break;
                    }
                }
                if (wi == null) {
                    String msg = "Selected service is not a wallpaper: "
                            + componentName;
                    if (fromUser) {
                        throw new SecurityException(msg);
                    }
                    Slog.w(TAG, msg);
                    return false;
                }
            }

			// 当壁纸服务支持在ambient模式下进行绘制的时候,需要检查是否有AMBIENT_WALLPAPER权限,
            if (wi != null && wi.supportsAmbientMode()) {
                final int hasPrivilege = mIPackageManager.checkPermission(
                        android.Manifest.permission.AMBIENT_WALLPAPER, wi.getPackageName(),
                        serviceUserId);
                if (hasPrivilege != PackageManager.PERMISSION_GRANTED) {
                    String msg = "Selected service does not have "
                            + android.Manifest.permission.AMBIENT_WALLPAPER
                            + ": " + componentName;
                    if (fromUser) {
                        throw new SecurityException(msg);
                    }
                    Slog.w(TAG, msg);
                    return false;
                }
            }
            // 检验完毕,这里才会开始bind 壁纸服务,如果校验失败的话,会返回false
			...
    }

上面的校验可以看出一共校验了三个条件:

  1. 启动的时候首先会校验这个壁纸服务是否声明权限为BIND_WALLPAPER权限, 该权限的定义在fwk/base/core/res/manifest.xml中,< permission android:name=“android.permission.BIND_WALLPAPER”
    android:protectionLevel=“signature|privileged” />
    该权限也是系统级别的,防止三方应用切换壁纸。
  2. 这个检查来校验服务是否声明了android.service.wallpaper.WallpaperService这个action。如果这个服务没有声明这个action的话那么,ris中就不会含有这个component信息。
  3. 获取名为android.service.wallpaper中的meta-data信息,该meta-data信息中提供了缩略图,开发者,简单的描述等。会将这些信息转换成WallpaperInfo。

2.绑定壁纸服务

壁纸服务的校验满足后,开始启动和绑定目标服务:


    private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
	// 校验服务是否符合要求结束后,开始着手启动服务
	...
	//1. 创建一个WallpaperConnection,该对象可以监听和WallpaperService之间的连接状态,同时归对象继承了IWallpaperConnection.Stub,这样该对象有拥有了跨进程通信的能力,当服务绑定成功后,onServiceConnected()方法调用中,WallpaperConnection实力会被发送到WallpaperService,该实例可以用于WallpaperService想WallpaperManagerService进行通信的桥梁。
	intent.setComponent(componentName);
    intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                    com.android.internal.R.string.wallpaper_binding_label);
    ...
    
    if (!mContext.bindServiceAsUser(intent, newConn,Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI| Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE | 
    Context.BIND_INCLUDE_CAPABILITIES,new UserHandle(serviceUserId))) {
}

	
	if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null && !wallpaper.equals(mFallbackWallpaper)) {
         detachWallpaperLocked(mLastWallpaper);
    }
    
    
	wallpaper.wallpaperComponent = componentName;
    wallpaper.connection = newConn;
    ...

bindWallpaperComponentLocked函数在拉起壁纸服务的时候主要做了下面几件事情:

  • 创建了WallpaperConnection对象,由于实现了ServiceConnection接口,所以WallpaperConnection可以用来监听和壁纸服务的连接状态,另外由于继承了IWallpoaperConnection.Stub接口,所以WallpaperConnection具有了跨进程通信的能力。
  • 启动壁纸服务:这里仅仅是拉起服务,和拉起普通服务的方式基本一致,拉起方式上则使用了bindServiceAsUser,查看官方注解,该接口增加了校验该用户是否能拉起该服务,其余的行为和bindService相同。
  • 保存当前WallpaperConnection实例,ConponentName,到WallpaperData中

bindWallpaperComponentLocked()函数将壁纸服务拉了起来,但是仅仅将壁纸服务拉起来是没有办法显示图像的,因为启动的服务并没有窗口令牌,这样就没办法添加窗口。剩下的这部分显示的工作在WallpaperConnection的onServiceConnected()方法中进行,在该回调中同样也能拿到壁纸服务端服务端提供的Binder对象。
WallpaperService在被bind的时候返回了一个IWallpaperServiceWrapper对象,从代码中可以看到,该对象中保存了WallpaperService实例,看了代码后再去理解这个对象的命名(包装WallpaperService),果然名副其实。


  class IWallpaperServiceWrapper extends IWallpaperService.Stub {
        private final WallpaperService mTarget;
        private IWallpaperEngineWrapper mEngineWrapper;

        public IWallpaperServiceWrapper(WallpaperService context) {
            mTarget = context;
        }
        
        @Override
        public void attach(IWallpaperConnection conn, IBinder windowToken,
        int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,int displayId) {
		...
		}

        @Override
        public void detach() {
        ...
        }
    }

该接口中一共有两个接口,attach和detach,attach接口在创建的时候可以将相关信息传递到壁纸服务中,对应的,detach接口在服务销毁的时候调用。

3.引擎的创建和初始化

引擎的创建准备工作开始于onServiceConnected()回调处,该回调会传递壁纸服务需要的窗口令牌和ServiceConnection对象等。
WallpaperManagerService.java


        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            synchronized (mLock) {
                if (mWallpaper.connection == this) {
                    mService = IWallpaperService.Stub.asInterface(service);
                    attachServiceLocked(this, mWallpaper);
                    // XXX should probably do saveSettingsLocked() later
                    // when we have an engine, but I'm not sure about
                    // locking there and anyway we always need to be able to
                    // recover if there is something wrong.
                    if (!mWallpaper.equals(mFallbackWallpaper)) {
                    	// 保存当前的壁纸信息到文件系统中,这样重启的时候就可以加载之前用户设置过的壁纸
                        saveSettingsLocked(mWallpaper.userId);
                    }
                    FgThread.getHandler().removeCallbacks(mResetRunnable);
                    mContext.getMainThreadHandler().removeCallbacks(mTryToRebindRunnable);
                }
            }
        }

onServiceConnected()函数中,首先将返回的binder对象进行了保存,然后在attachServiceLocked()方法中会调用connectLocked()方法,connectLocked()接口中调用了attach方法传递了壁纸服务所需要的信息。


            void connectLocked(WallpaperConnection connection, WallpaperData wallpaper) {
                ...
                mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId);
                final DisplayData wpdData = getDisplayDataOrCreate(mDisplayId);
                try {
                    connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
                            wpdData.mWidth, wpdData.mHeight,
                            wpdData.mPadding, mDisplayId);
                }
                ...
            }

attach接口回传了许多信息,其中

  • connection为WallpaperConnection的实例。WallpaperConnection之所以具有跨进程通信的能力是因为继承了IWallpaperConnection.Stub类,该Stub对象中比较重要的一个接口就是attachEngine(),因为Engine实现才是动态壁纸的核心,WallpaperService会将创建好的Engine引用通过attachEngine()回传给WallpaperManagerService进行管理。
  • mToken是向WMS注册过的窗口令牌,只有拥有了这个令牌,WallpaperService才有权添加壁纸窗口。

传递了WallpaperService需要的信息之后,WallPaperService开始进行引擎的创建。查看WallpaperService中attach()方法的实现,


    class IWallpaperServiceWrapper extends IWallpaperService.Stub {
        ...
        @Override
        public void attach(IWallpaperConnection conn, IBinder windowToken,
                int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
                int displayId) {
            mEngineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken,
                    windowType, isPreview, reqWidth, reqHeight, padding, displayId);
        }
        ...
     }

attach方法创建了一个IWallpaperEngineWrapper,顾名思义,该对象有壁纸服务创建的引擎的引用,在创建IWallpaperEngineWrapper对象的时候,会发送DO_ATTACH消息,该消息用于壁纸服务引擎的创建,


IWallpaperEngineWrapper.java
        IWallpaperEngineWrapper(WallpaperService context,
                IWallpaperConnection conn, IBinder windowToken,
                int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
                int displayId) {
            mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
        	...
            Message msg = mCaller.obtainMessage(DO_ATTACH);
            mCaller.sendMessage(msg);
        }
...
		@Override
        public void executeMessage(Message message) {
            switch (message.what) {
                case DO_ATTACH: {
                    try {
                    // 将IWallpaperEngineWapper对象传递给WallpaperConnection进行保存,通过这个引用,WallpaperManagerService也可以通过它与engine进行通信
                        mConnection.attachEngine(this, mDisplayId);
                    } catch (RemoteException e) {
                        Log.w(TAG, "Wallpaper host disappeared", e);
                        return;
                    }
                    // 创建一个引擎,该方法为抽象方法,需要子类根据自身实现具体的引擎
                    Engine engine = onCreateEngine();
                    mEngine = engine;
                    mActiveEngines.add(engine);
					// 该方法中会完成窗口的创建,surface创建等工作。
                    engine.attach(this);
                    return;
                }

由于mConnection.attachEngine()方法将IWallpaperEngineWrapper传递给了WallpaperManagerService,因此WallpaperManagerService可以转发相关的请求和设置到Engine对象中,实现WallpaperManagerService到壁纸的通信。
onCreateEngine方法执行后,引擎创建完成,之后通过engine.attach()方法进行引擎相关的初始化:


        void attach(IWallpaperEngineWrapper wrapper) {
            ...
            mIWallpaperEngine = wrapper;
            mCaller = wrapper.mCaller;
            mConnection = wrapper.mConnection;
            mWindowToken = wrapper.mWindowToken;
            mSurfaceHolder.setSizeFromLayout();
            mInitializing = true;

			// 这个session用于和WMS进行通信
            mSession = WindowManagerGlobal.getWindowSession();

			// mWindow是一个IWindow对象,用于接收从WMS发送过来的消息
            mWindow.setSession(mSession);

            mLayout.packageName = getPackageName();
            mIWallpaperEngine.mDisplayManager.registerDisplayListener(mDisplayListener,
                    mCaller.getHandler());
            mDisplay = mIWallpaperEngine.mDisplay;
            mDisplayContext = createDisplayContext(mDisplay);
            mDisplayState = mDisplay.getState();

            if (DEBUG) Log.v(TAG, "onCreate(): " + this);
            // 子类可以重写该接口,在该接口中可以修改mSurfaceHolder相关的属性,这个时候
            // 窗口尚未创建。设置的相关属性将在updateSurface中创建窗口时使用
            onCreate(mSurfaceHolder);

            mInitializing = false;

            mReportedVisible = false;
			
			// updateSurface会进行窗口以及Surface的创建。
            updateSurface(false, false, false);
        }

attach方法执行的完成,标志着壁纸启动的完成,之后可以调用壁纸的surface显示图像。

壁纸服务的启动流程总结

壁纸服务的启动相比于普通服务的启动较为复杂,接下来用下面的示意图对整体的流程进行梳理:

在这里插入图片描述

壁纸服务在启动的时候,大体可以分为两个阶段,首先就要是拉起对应的服务,拉起服务后然后将WindowToken等参数传递给引擎进行窗口的创建,surface的创建。在WallpaperManagerService和WallpaperService交互的过程中,主要有下面三个跨进程通信的Binder对象:

  • WallpaperConnection:实现在WallpaperManagerService中,并通过IWallpaperService.attach回调传递给了IWallpaperEngineWrapper,通过WallpaperConnection.attachEngine()方法,WallpaperService将IWallpaperEngineWrapper回传给了WallpaperManagerService,实现了双向的通信。
  • IWallpaperService:实现在WallpaperService中,该对象提供了attach方法,用于从WallpaperManagerService获取引擎创建时需要的WindowToken等信息。
  • IWallpaperEngineWrapper:实现在壁纸服务进程中,同时引用交给了WallpaperManagerService,该对象封装了Engine类,WallpaperManagerService对引擎相关的控制需要通过该对象提供的接口实现。

自己最近因为需要定位一个开机壁纸服务启动慢的问题,所以熟悉了下壁纸服务的启动过程,在此记录启动流程。定位该问题梳理从bind到onServiceConnected和引擎相关初始化,对比每一个阶段的时间,最终确定问题的原因。
参考:

  • 《深入理解Android卷3》第八章:深入理解android壁纸服务
  • framework-base源码

到此这篇关于详解Android壁纸服务的启动过程的文章就介绍到这了,更多相关Android壁纸内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

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

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

详解Android壁纸服务的启动过程

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

下载Word文档

猜你喜欢

Android应用框架之应用启动过程详解

在Android的应用框架中,ActivityManagerService是非常重要的一个组件,尽管名字叫做ActivityManagerService,但通过之前的博客介绍,我们知道,四大组件的创建都是有AMS来完成的,其实不仅是应用程序
2022-06-06

详解Android广播Broadcast的启动流程

Android中的广播是一种用于应用程序之间通信的机制。它允许应用程序发送和接收系统级或应用程序级的广播消息。当一个广播被发送时,系统会自动启动广播接收器来处理该广播。下面是Android广播的启动流程:1. 广播发送:应用程序发送一个广播
2023-08-11

[图解]Android源码分析——Activity的启动过程

Activity的启动过程一.Launcher进程请求AMSLauncher.java的startActivitySafely方法的执行过程:Activity.java中startActivity方法的执行过程:startActivityF
2022-06-06

MySQL8.0服务无法正常启动的解决过程

目录mysql8服务无法正常启动的解决(1053错误)第一种方法第二种方法MySQL8服务无法正常启动的解决(1069错误)MySQL8.0 服务无法启动 3534总结一下MySQL8服务无法正常启动的解决(1053错误)第一种方法初始
2022-12-26

Redis服务器的启动过程分析

本文将通过分析代码来介绍Redis的启动过程,通过查看Redis 的启动脚本,得知Redis的启动时从Redis.c的main方法开始的。Redis启动可以分为以下几个步骤: 1.初始化Redis服务器全局配置 2.重置服务器Save参数(
2022-06-04

android studio2.3如何编译动态库的过程详解

前言最近在工作中需要编译android下的动态库,本以为是一件简单的事,没想到因为工具,以及google本身被墙的原因,折腾了好久。在windows外的平台搞事情,寿命都得缩短。过程如下一种方案是用eclipse+ndk+adt插件,总之是
2023-05-30

Tomcat服务器的配置与启动全过程

这篇文章主要介绍了Tomcat服务器的配置与启动全过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-03-20

mysql安装过程启动服务器失败怎么解决

在MySQL安装过程中启动服务器失败可能是由于多种原因造成的,可以尝试以下方法来解决问题:检查日志文件:查看MySQL的错误日志文件,通常位于MySQL的数据目录下,查看日志文件可以帮助定位问题所在。检查端口占用:确保MySQL所使用的端口
mysql安装过程启动服务器失败怎么解决
2024-04-20

WinXP开启Computer Browser服务的教程详解

  当我们说到Computer Browser,应该很多用户都非常的了解,是系统中的一项服务,当我们在操作电脑中关闭了它,那么可能就会导致不能共享,所以就需要用户开启它,不过在WinXP系统中怎样开启Computer Browser服务的
2023-05-22

Oracle 数据库启动过程的三阶段、停库四种模式详解

目录数据库的启动过程(3个台阶)1.nomount2.mount3.open数据库的启动过程(3个台阶)1.nomountshutdown --> nomountstartup nomountselect status from v
2022-11-21

编程热搜

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

目录