android 屏幕投射

If you’re anything like me, who has never created a screen-cast app before and want to explore the options to do it, let’s dive in together.

如果您和我一样,以前从未创建过截屏应用程序,并且想探索实现此目的的选项,那么让我们一起来研究一下。

Things you should know already- Creating Android apps (used Kotlin for this post)

您应该已经知道的事情-创建Android应用程序(本文使用过Kotlin)

屏幕镜像或屏幕投射 (Screen mirroring or screen casting)

For most users, screen mirroring and screen casting might just mean the same thing: presenting the content from phone or any other small device to TV. It’s crucial to know the difference though, if you’re integrating one of these into your own apps.

对于大多数用户而言,屏幕镜像和屏幕投射可能只是同一件事:将内容从电话或任何其他小型设备呈现到电视。 但是,如果您要将其中之一集成到自己的应用程序中,则要知道它们之间的区别至关重要。

Screen Mirroring refers to showing your phone’s display on the TV as is. That means any clutter on the phone, navigation icons, and every other clickable item appears on the TV as well, even though on most TVs you can’t interact with those UI elements.

屏幕镜像是指按原样在电视上显示手机的显示屏。 这意味着电话上的任何杂物,导航图标以及所有其他可单击的项目也会出现在电视上,即使在大多数电视上,您也无法与这些UI元素进行交互。

Screen Casting means showing only the meaningful content on the TV. It’s used when you want to play a full-screen video on TV while your phone is showing controls, or maybe show graphs and charts on TV while your phone displays data in tabular format.

屏幕投射意味着仅在电视上显示有意义的内容。 当您要在手机显示控件时在电视上播放全屏视频,或者当手机以表格格式显示数据时在电视上显示图形和图表时,可以使用此功能。

规约 (Protocols)

The top most common protocols used for casting are:1. ChromeCast (Google Cast)2. MiraCast3. AirPlay4. Amazon Fling5. DLNA6. DIAL7. Samsung SmartTV

用于转换的最常见的协议是:1。 ChromeCast(Google Cast)2。 MiraCast3。 AirPlay4。 亚马逊物流5。 DLNA6。 拨号7。 三星SmartTV

We’ll focus on the first two of the list. I might soon add AirPlay and Amazon Fling also (although AirPlay works on Apple devices only).

我们将重点关注列表的前两个。 我可能很快还会添加AirPlay和Amazon Fling(尽管AirPlay仅适用于Apple设备)。

MiraCast (MiraCast)

MiraCast is essentially a screen-mirroring protocol. The typical use case is to mirror entire screen of your small device (phone or tablet), on a bigger device (TV). It is possible to override system behaviour and display our own (custom) views using MiraCast as well, which from a developer’s perspective will be a second screen being displayed on top of the mirrored screen.

MiraCast本质上是一个屏幕镜像协议。 典型的用例是在较大的设备(TV)上镜像小型设备(电话或平板电脑)的整个屏幕。 也可以使用MiraCast覆盖系统行为并显示我们自己的(自定义)视图,从开发人员的角度来看,这将是第二个屏幕显示在镜像屏幕的顶部。

On Android, MiraCast devices aren’t visible to app unless that MiraCast device is already selected to cast from the system settings (at least from Android 8.0).

在Android上,除非已选择从系统设置(至少从Android 8.0开始)投射MiraCast设备,否则应用程序将看不到MiraCast设备。

Once a MiraCast device is selected from settings, the app can then use MediaRouter API or DisplayManager API to get hold of a Display object, which can then be provided to Presentation object.

从设置中选择MiraCast设备后,应用程序便可以使用MediaRouter API或DisplayManager API来获取Display对象,然后可以将其提供给Presentation对象。

You can look for the selected MiraCast device by checking for selected route in the onResume method of the Activity or a Fragment.

您可以通过在“活动”或“片段”的onResume方法中检查选定的路线来查找选定的MiraCast设备。

override fun onResume() {    findSelectedDevice()}private fun findSelectedDevice() {    val route = mediaRouter.selectedRoute    if (route.playbackType != PLAYBACK_TYPE_LOCAL && route.presentationDisplay != null) {        selectedDisplay = route.presentationDisplay        showPresentationDialog()}

A Presentation object is just a common Android dialog, when you call its show() method, instead of being presented to screen, this dialog opens in fullscreen mode on the TV. Now you have any layout in that presentation dialog.

Presentation对象只是一个常见的Android对话框,当您调用它的show()方法时,该对话框以全屏模式在电视上打开,而不是显示在屏幕上。 现在,您可以在该演示文稿对话框中进行任何布局。

private fun showPresentationDialog() {    if (selectedDisplay == null)        return    if (presentationDialogFragment != null && presentationDialogFragment!!.presentationDisplay != selectedDisplay) {        dismissPresentationDialog()    }    if (presentationDialogFragment == null) {        presentationDialogFragment = MyCustomPresentation(this, selectedDisplay)        try {            presentationDialogFragment!!.show(activity!!.supportFragmentManager, "MY_PRESENTATION_DIALOG_FRAGMENT")        }        catch (e: WindowManager.InvalidDisplayException) {            Toast.makeText(context, "Invalid cast display", Toast.LENGTH_LONG).show()        }    }}

Now create your own MyCustomPresentation class that inherits from Presentation.

现在,创建自己的MyCustomPresentation类,该类继承于Presentation

You can also inherit from DialogFragment and then inside its nnCreateDialog return a Presentation object.

您也可以从 DialogFragment 继承 ,然后在其 nnCreateDialog 内部 返回一个Presentation对象。

class MyCustomPresentation(private val display: Display) : DialogFragment() {    override fun onCreateDialog(savedInstanceBundle: Bundle?) : Dialog {        return Presentation(context, display)    }override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {        super.onCreateView(inflater, container, savedInstanceState)return inflater.inflate(R.layout.my_layout, container, false)    }}

Then you can add views inside my_layout.xml however you want.

然后,您可以根据需要在my_layout.xml中添加视图。

To test this, run the application and go to settings > cast/smart view/or similar name. Choose your MiraCast device, come back to the application and you should see your layout on the TV.

要对此进行测试,请运行该应用程序,然后转到设置>演员表/智能视图/或类似名称。 选择您的MiraCast设备,返回到应用程序,您应该在电视上看到布局。

It’s reasonable to think that all the processing of the views for supporting MiraCast on your app has to be done by the phone itself instead of the TV. Under the hood, the phone is probably rasterizing this Presentation view into a video and then sending it out to TV.

可以合理地认为,为支持您的应用程序上的MiraCast而进行的所有视图处理都必须由手机本身而不是电视来完成。 在引擎盖下,手机可能会将此Presentation视图栅格化为视频,然后将其发送到电视。

ChromeCast (ChromeCast)

ChromeCast until recently supported this behaviour from MiraCast as well. You could get hold of Display object, using CastRemoteDisplayLocalService class. Then provide that Display object to Presentation object, and work in the same way as you did with MiraCast. However, this behaviour is not supported anymore and doesn’t work now.

直到最近,ChromeCast都支持MiraCast的这种行为。 使用CastRemoteDisplayLocalService类可以获取Display对象。 然后将该Display对象提供给Presentation对象,并以与MiraCast相同的方式工作。 但是,此行为不再受支持,现在不起作用。

Instead with ChromeCast, you have two options: one is to use a Default Media Receiver (or a Styled Media Receiver which is the same as Default Media Receiver except it can be styled using CSS), another option is to use Custom Receiver.

取而代之的是使用ChromeCast,您有两种选择:一种是使用默认媒体接收器 (或与默认媒体接收器相同的样式化媒体接收器,但可以使用CSS对其进行样式设置),另一种选择是使用自定义接收器

With the Default Media Receiver, you can present any media content (videos, photos, and audio) in supported formats (mp4, jpeg, and all common formats) to the TV without needing to do any hard work on the TV side. With the Default Media Receiver, you can even present live video content in one of the three supported protocols: HLS, DASH, and SmoothStreaming.

使用默认媒体接收器,您可以将任何支持的格式(mp4,jpeg和所有常见格式)的媒体内容(视频,照片和音频)呈现给电视,而无需在电视方面进行任何艰苦的工作。 使用默认媒体接收器,您甚至可以三种受支持的协议之一呈现实时视频内容:HLS,DASH和SmoothStreaming。

However, if you need to show your own custom content on ChromeCast (this means showing non media elements like text, charts, or virtually anything), you need to create an app for the ChromeCast.

但是,如果您需要在ChromeCast上显示自己的自定义内容(这意味着显示非媒体元素,如文本,图表或几乎所有内容),则需要为ChromeCast创建一个应用。

The ChromeCast app is essentially a web-application running inside a Google Chrome instance in a full screen mode without showing address bar, menus, and all other chrome.

ChromeCast应用本质上是一个全屏模式在Google Chrome实例内部运行的网络应用,不显示地址栏,菜单和所有其他镶边。

For creating a ChromeCast application, you need cast application id which you can get by registering on Google Cast Dev Console and creating a new application in that console. The registration fee is US $5 as of date of writing this post. Application URL is also set up here.

要创建ChromeCast应用程序,您需要投放应用程序ID ,该ID可通过在Google Cast Dev Console上注册并在该控制台中创建新应用程序来获得。 截至撰写本文时,注册费为5美元。 应用程序URL也在这里设置。

For your application to be seen by your ChromeCast device while it is not published yet, you also need to register your ChromeCast device’s serial number in the Cast Dev Console.

为了使您的应用程序在尚未发布的情况下仍能被您的ChromeCast设备看到,您还需要在Cast Dev Console中注册ChromeCast设备的序列号。

Once registered, you can start coding from the sender side (phone application) and the TV side (web-application) in order to establish custom communication channels.

注册后,您可以从发送方(电话应用程序)和电视方(Web应用程序)开始编码,以便建立自定义的通信渠道。

发件人应用 (Sender App)

Create a class that inherits from OptionsProvider. This class is referenced from AndroidManifest.xml

创建一个从OptionsProvider继承的类。 此类从AndroidManifest.xml引用

import com.google.android.gms.cast.framework.OptionsProvider// other importsclass CastOptionsProvider : OptionsProvider {    companion object {        fun getCastAppIdFromContext(context: Context): String {            return context.resources.getString(R.string.cast_app_id)        }    }    override fun getCastOptions(context: Context): CastOptions {        return CastOptions.Builder()            .setReceiverApplicationId(getCastAppIdFromContext(context))            .build()    }    override fun getAdditionalSessionProviders(context: Context?): MutableList<SessionProvider>? {        return null    }}

Add these lines to AndroidManifest.xml. Make sure class paths are matched according to your own project.

将这些行添加到AndroidManifest.xml。 确保根据您自己的项目匹配类路径。

<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"    android:value="com.mycompany.myapp.utils.CastOptionsProvider" /><meta-data    android:name="com.google.android.gms.version"    android:value="@integer/google_play_services_version" />

Create a menu layout which contains cast button. Use AndroidX library’s MediaRouteActionProvider which will handle all the cast related things and change the state of the button automatically.

创建一个包含投射按钮的菜单布局。 使用AndroidX库的MediaRouteActionProvider ,它将处理所有与投射相关的事情,并自动更改按钮的状态。

<?xml version="1.0" encoding="utf-8"?><menu xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto">    <item android:id="@+id/media_route_menu_item"        android:title="@string/media_route_menu_title"app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"        app:showAsAction="always"        /></menu>

The inside your Fragment or Activity, override onCreateOptionsMenu. You might also need to call setHasOptionsMenu(true) inside onCreate.

在Fragment或Activity的内部,覆盖onCreateOptionsMenu 。 您可能还需要在onCreate内调用setHasOptionsMenu(true)

override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {    inflater.inflate(R.menu.trainee_conference_menu, menu)    val mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item)    val mediaActionProvider = MenuItemCompat.getActionProvider(mediaRouteMenuItem) as MediaRouteActionProvider    mediaActionProvider.setAlwaysVisible(true)    mediaRouteSelector.also(mediaActionProvider::setRouteSelector)}

The last line of code will attach this cast button to a MediaRouteSelector object. You can configure the type of devices that should be visible to the app using mediaRouteSelector.

代码的最后一行将将此强制转换按钮附加到MediaRouteSelector对象。 您可以使用mediaRouteSelector配置对应用程序可见的设备类型。

val mediaRouteSelector = MediaRouteSelector.Builder()        .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)        .addControlCategory(CastMediaControlIntent.categoryForCast(CAST_APP_ID))        .build()

By using CastMediaControlIntent.categoryForCast(CAST_APP_ID), you’re enabling the devices that you’ve registered from Dev Console to be visible to the app.

通过使用CastMediaControlIntent.categoryForCast(CAST_APP_ID) ,可以使从Dev Console注册的设备对应用程序可见。

Create a MediaRouter item, a SessionManager item and register their callbacks. MediaRouter callbacks will handle when the cast device has been selected, unselected, or switched. SessionManager callbacks will let us know when the ChromeCast receiver app is loading, or loaded, or closing, or closed.

创建一个MediaRouter项,一个SessionManager项并注册其回调。 当选择,取消选择或切换了投射设备时,MediaRouter回调将进行处理。 当加载,加载或关闭或关闭ChromeCast接收器应用程序时,SessionManager回调将通知我们。

val mediaRouter = MediaRouter.getInstance(application.applicationContext)private var castSessionManager: SessionManager = CastContext.getSharedInstance(application.applicationContext).sessionManageroverride fun onResume() {    // ... lines above ...    mediaRouter.addCallback(mediaRouteSelector, mediaRouterCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN)castSessionManager.addSessionManagerListener(sessionManagerListener)}override fun onPause() {    // ... lines above    mediaRouter.removeCallback(mediaRouterCallback)castSessionManager.removeSessionManagerListener(sessionManagerListener)}

Define MediaRouter callback functions

定义MediaRouter回调函数

// You can write your own logic in each of these functionsprivate val mediaRouterCallback = object: MediaRouter.Callback() {        override fun onRouteSelected(router: MediaRouter?, route: MediaRouter.RouteInfo?) {        }override fun onRouteUnselected(router: MediaRouter?, route: MediaRouter.RouteInfo?) {        }override fun onRoutePresentationDisplayChanged(router: MediaRouter?, route: MediaRouter.RouteInfo?) {        }override fun onRouteChanged(router: MediaRouter?, route: MediaRouter.RouteInfo?) {        }}

Define SessionManager callback funtions

定义SessionManager回调函数

private val sessionManagerListener = object: SessionManagerListener<Session> {    override fun onSessionStarted(session: Session?, sessionId: String?) {        castSession = session as? CastSession        castSessionEnabled = true        sendTestLogMessage()    }    override fun onSessionResumeFailed(p0: Session?, p1: Int) {    }    override fun onSessionSuspended(p0: Session?, p1: Int) {        castSessionEnabled = false    }    override fun onSessionEnded(p0: Session?, p1: Int) {        castSessionEnabled = false    }    override fun onSessionResumed(p0: Session?, p1: Boolean) {        castSessionEnabled = true    }    override fun onSessionStarting(p0: Session?) {    }    override fun onSessionResuming(p0: Session?, p1: String?) {    }    override fun onSessionEnding(p0: Session?) {    }    override fun onSessionStartFailed(p0: Session?, p1: Int) {    }}

Send message to cast device

发送消息至演员表

// I used Timber for logging. You can just use Log.d(...)fun sendMessageToChromecastDevice(namespace: String, jsonMessage: String, successCallback: (() -> Unit)? = null) {    if (castSessionEnabled == true) {        try {            castSession?.sendMessage(namespace, jsonMessage)                ?.setResultCallback { status ->                    Timber.d("Message to $namespace $status")                    if (status.isSuccess) {                        successCallback?.invoke()                    }                }        }        catch (e: Exception) {            Timber.e(e)        }    }}

Namespaces are crucial for establishing communication channels. For ChromeCast the namespaces must start with “urn:x-cast:”. For example, you can have a namespace defined like this:

命名空间对于建立通信渠道至关重要。 对于ChromeCast,名称空间必须以“ urn:x-cast:”开头。 例如,您可以具有如下定义的名称空间:

val LOG_NAMESPACE = "urn:x-cast:com.mycompany.myapp.Log"

Let’s see how sendTestLogMessage will look like:

让我们看看sendTestLogMessage的样子:

// You can call this function on some button click eventsfun sendTestLogMessage() {    val jsonMessage = Gson().toJson(LogMessageOut(message = "Screen touched at ${LocalDateTime.now()}"))    sendMessageToChromecastDevice(LOG_NAMESPACE, jsonMessage) {        // success callback code        Toast.makeText(context, "Logged", Toast.LENGTH_LONG).show()    }}

LogMessageOut class:

LogMessageOut类:

data class LogMessageOut (    @SerializedName("message")    var message: String? = null)

At this point, your sender app is able to configuring ChromeCast settings to open the application (web-application whose URL is defined inside Dev Console), responding to cast device select/unselect events, responding to receiver app lifecycle events, creating communication channels using namespaces, and sending test log messages.

此时,您的发送方应用程序可以配置ChromeCast设置以打开应用程序(其URL在Dev Console中定义的Web应用程序),响应演员表选择/取消选择事件,响应接收方应用程序生命周期事件,使用以下方式创建通信渠道命名空间,并发送测试日志消息。

接收器应用 (Receiver app)

Building a receiver app that listens to log messages from the sender app and printing them on the screen is trivial.

构建一个接收方应用程序以侦听来自发送方应用程序的日志消息并将其打印在屏幕上很简单。

First, create an html (preferably index.html) file with the following content:

首先,创建具有以下内容的html(最好是index.html)文件:

<html><head><script type="text/javascript" src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script></script></head><body><div style="color: red; font-size: 108pt;">My App</div><div id="logger" style="color: white;"></div><script src="cast_script.js"></script></body></html>

Create cast_script.js with the following content:

创建具有以下内容的cast_script.js:

var LOG_NAMESPACE = "urn:x-cast:com.mycompany.myapp.Log"var castReceiverContext = cast.framework.CastReceiverContext.getInstance();castReceiverContext.addCustomMessageListener(LOG_NAMESPACE, function (customEvent) {logElement.innerText += LOG_NAMESPACE + " - " + customEvent.data.message + "\n";console.log(LOG_NAMESPACE + " - " + customEvent.data.message);});var logElement = document.querySelector("#logger");logElement.innerText = "Logging Events\n\n";// JSON is the default type, but you can be sure anywaycastReceiverOptions.customNamespaces = Object.assign({});castReceiverOptions.customNamespaces[LOG_NAMESPACE] = cast.framework.system.MessageType.JSON;castReceiverContext.start(castReceiverOptions);

Host the receiver app files on the URL you have provided to the Dev Console. For hosting on a local server, you can use a localhost tunnelling service like ngrok or pagekite because ChromeCast won’t recognize local servers normally.

将接收器应用程序文件托管在您提供给开发控制台的URL上。 要在本地服务器上托管,您可以使用localhost隧道服务(例如ngrok或pagekite),因为ChromeCast无法正常识别本地服务器。

For debugging the ChromeCast receiver application, you can go to chrome://inspect on Google Chrome, or edge://inspect on Mircosoft Edge. (For me, Chrome inspector wasn’t working fine on 1st gen ChromeCast, but worked fine on Edge.)

要调试ChromeCast接收器应用程序,可以转到Google Chrome上的chrome:// inspect ,或在Mircosoft Edge上访问edge:// inspect 。 (对我来说,Chrome检查器在第一代ChromeCast上运行不正常,但在Edge上运行良好。)

Also, ChromeCast 1st gen wasn’t updated after Chrome 70.x while ChromeCast 2nd gen was running on Chrome 76.x. So, some ES6 features didn’t work on ChromeCast 1st gen and caused a lot of trouble for me. For that purpose, I made use of Babel to convert ES6 code into ES5.

此外,ChromeCast 1st gen在Chrome 70.x之后没有更新,而ChromeCast 2nd gen在Chrome 76.x上运行。 因此,某些ES6功能在ChromeCast 1st gen上不起作用,给我带来了很多麻烦。 为此,我利用Babel将ES6代码转换为ES5。

翻译自: https://medium.com/swlh/casting-custom-content-from-android-app-to-tv-2020-approach-ea463888f7b

android 屏幕投射


http://www.taodudu.cc/news/show-5964893.html

相关文章:

  • 机械表矢量素材分享(三眼表盘意向:过去、现在、未来)
  • Qt汽车自定义仪表盘控件
  • QT绘制仪表盘
  • Android简易仪表盘
  • 73.qt quick-通用可拖拽表盘示例(支持渐变)
  • 【QT 5 相关实验-仪表盘-学习笔记-表盘组件练习与使用总结】
  • android带指针仪表盘自动移动,Android 绘制仪表盘指针刻度
  • 如何更改Apple Watch上的表盘
  • wxid转微信
  • 怎么转换wxid?? 怎么添加wxid的号码???
  • wxid转微信号
  • 【分享】如何在集简云更换账户的微信、邮箱、手机号?
  • php微信号轮播系统,微信小程序的轮播功能
  • WXID怎么转换成微信号
  • 锚链接html target,列锚标签()的target属性中,哪一个可以定义在新窗口中打开链接地址()。 - 问答库...
  • camera资料链接整理
  • 载纳米雄黄磁性白蛋白纳米球/壳聚糖修饰/As2O3磁性Fe3O4白蛋白微球
  • batocera中文整合包_分享一个整合 SSM 框架的高并发和商品秒杀项目
  • windows server 2008R2/2012R2安装
  • 友情代码c语言,我的友情链接
  • 【看表情包学Linux】软硬链接
  • inode和软硬链接
  • 文件系统与软硬链接
  • 微软Windows10LTSC2019官方三月更新版镜像
  • js实现磁性吸附
  • Linux的文件系统和软硬链接
  • VS code 格式化设置代码标签不换行
  • tinymce 去掉编辑器换行默认增加的p标签
  • vscode标签内属性代码自动换行问题
  • android TextView 可自定义图标的带角标文字组件

android 屏幕投射_将自定义内容从Android应用投射到电视(2020年方法)相关推荐

  1. android 屏幕最小宽度_如何找到所有可用Android手机的最小和最大屏幕高度/宽度比?...

    我希望我的应用能够在任何Android设备上正确显示,而不管屏幕尺寸,屏幕密度或屏幕高宽比如何.我想支持API 8之前的Android设备.我可以为纵向,横向和xlarge屏幕创建单独的布局,但除此之 ...

  2. android 屏幕最小宽度_最小宽度360dp Android屏幕适配—被偷走的dp - 硬件设备 - 服务器之家...

    最小宽度360dp Android屏幕适配-被偷走的dp 发布时间:2017-05-11 来源:服务器之家 一.Android手机屏幕DPI DPI概念自行百度 Android手机按照不同DPI分为: ...

  3. 8屏幕滚动_对标iOS?Android 11或无缘屏幕长截图

    一直以来,不少用户都很喜欢用长截图功能进行页面截图,可以说在日常应用中这是一项比较实用的功能.目前比较主流的小米MIUI.华为EMUI.三星OneUI等第三方 Android 定制 UI都对这一功能进 ...

  4. android 设备名称_如何更改您的Android TV的设备名称

    android 设备名称 Android TV is Google's attempt at taking over the living room, and with some units bein ...

  5. android 屏幕长截图,对标iOS?Android 11或无缘屏幕长截图

    原标题:对标iOS?Android 11或无缘屏幕长截图 一直以来,不少用户都很喜欢用长截图功能进行页面截图,可以说在日常应用中这是一项比较实用的功能. 目前比较主流的小米MIUI.华为EMUI.三星 ...

  6. android 流量消耗_如何查看正在消耗Android设备存储空间的内容

    android 流量消耗 It's all too easy to stuff your Android phone or tablet to the gills with data and find ...

  7. android 印度语_开放获取内容如何帮助推动印度语维基百科的增长

    android 印度语 在印度农村 ,移动Internet连接正在Swift增长,并且由于大多数Internet用户使用其本国语言更加自在,因此使用印度语言制作内容的网站将推动这一增长. 在像印度这样 ...

  8. android aar项目_介绍如何调试Xamarin.Android的binding项目

    背景 Xamarin的开发的一个无法避免的弊端就是在很多Android原生态应用中被普遍用到的库,未必有.NET的实现版本.这个问题就如同当时微软WinPhone失败的原因一样,在另外两个平台中非常普 ...

  9. android 输入法更换_一种动态切换Android系统输入法的弹出模式的方法与流程

    本发明涉及一种Android系统利用动态切换输入法的弹出模式解决输入法跳闪抖动问题的方法,属于安卓系统技术领域. 背景技术: 随着Android系统的快速发展以及安卓手机的不断普及,基于Android ...

最新文章

  1. Mobileye独创性创新
  2. Android应用开发基础篇(12)-----Socket通信(转载)
  3. iphone开发证书 纠结许久
  4. Unity FixedUpdate 与 Update 的线程关系实验
  5. asp.net控件库FineUI使用入门图解
  6. C++:线程操作之CRITICAL_SECTION用法的介绍和例子理解
  7. 有关Rating组件的研究——Silverlight学习笔记[41]
  8. js与C#服务端 json数据交互
  9. deepin安装bochs2.6.2_2.kafka安装与使用
  10. php对接银行接口,php 银行接口开发写法
  11. java 1.8 or higher_NetBeans安装提示neatbeans cannot find java 1.8 or higher
  12. extjs简单分页grid的总结
  13. Unix编程艺术:哲学基础
  14. 计算机设备管理器被禁用,win系统管理员被禁用在安全模式的解决方法
  15. 【Programe.VIM学习】
  16. 单字节的乘法指令设计汇编程序11*12
  17. javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher su 解决办法
  18. Autofac基础知识学习
  19. markdown中编辑数学公式用到的技巧
  20. 技术人员如何创业《四》—— 打造超强执行力团队

热门文章

  1. 经典微信营销案例排行榜
  2. 搭建docker,docker搭建达梦数据库,详细【图文】
  3. 《Java之面向对象:下》
  4. Software Performance Testing - Gatling测试脚本编写
  5. coreldraw x5安装视频教程_CorelDRAW 2020详细安装图文教程指导
  6. 如何提升美团渠道的快消商品销售-从搜索行为和转化率分析|倪钰桐谈电商
  7. 区块链的隐私怎样保护?
  8. opencv 识别长方形_使用OpenCV从图像中检测最大矩形
  9. 关于前端接口 jquery的ajax请求
  10. 华为devcloud使用git代码托管