Timed out waiting for process (xxx) to appear on错误

Timed out waiting for process com.xxx to appear on xx 错误

最近在做Flutter的开发,有段时间没动原生代码了。上周由于有个App问题必须修改,又把原生代码翻出来,可以点击AndroidStudio菜单栏中的运行按钮后,build过程迅速结束,开始还以为新版AS有啥改进呢。可仔细一看run窗口的log,报错内容大概如下:

Launching 'app' on xxx
Timed out waiting for process (xxx) to appear on Phone

似乎根本就没有执行安装apk到手机,再查看build目录,发现apk也没有生成。尝试直接执行gradle命令,installRelease,installDebug等命令均可正常执行,app也安装到手机上。

开始在网上搜索关于“Timed out waiting for process (xxx) to appear on”,大概有如下方法:
1.把调试配置中的启动选项从Nothing设置为Default Activity即可
2.File > Invalidate Caches / Restart… > Just Restart
3.Gradle配置中加debuggable true
4.AndroidStudio版本问题
5.re-starting adb
当然还有别的各种方法,统统试过了,没有任何效果。开始怀疑是AndroidStudio版本问题,想去看看有没有新版,发现已经是最新版了。
偶然间重启的时候,AS右下角弹出个错误提示,点开后,发现是HMS Tools好像运行报错了。反正华为这个服务也就觉得太臃肿了,早就想卸载了。正好试一下,哈哈,没想到。卸载完之后,一切正常了。
看来着罪魁祸首就是HMS啊。再也不装这个插件了。拜拜了您呢。
————————————————
版权声明:本文为CSDN博主「hiperion」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ocean20/article/details/125593999

Task :app:mergeDebugResources FAILED

Task :app:mergeDebugResources FAILED
ERROR:[drawable-xhdpi-v4/node_modules_reactnavigation_elements_src_assets_backicon] /Users/../Documents/wl/src/chatapp/android/app/src/main/res/drawable-xhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png [drawable-xhdpi-v4/node_modules_reactnavigation_elements_src_assets_backicon] /Users/../Documents/wl/src/chatapp/android/app/build/generated/res/react/debug/drawable-xhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png: Resource and asset merger: Duplicate resources

 

删掉 /Users/../Documents/wl/src/chatapp/android/app/build/generated/res/react/debug/drawable-xhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png

 

原因是,一开始

bundleInDebug: false,

后来改成

bundleInDebug: true,

导致打包了rn的其他资源

安装WordPress中文包四步曲

使用国外空间如Godaddy等写blog的朋友,相比都会直接安装空间商提供的英文版WordPress,理由很简单,一个按钮,就能自动完成。这样,问题就来了,如果想让blog前台后台都显示中文,那就还要装个中文包了。

方法就两步:

  1. 下载wordpress中文团队开发维护的简体中文语言包
  2. 在wordpress/wp-content下创建languages目录;
  3. 将中文包中的文件zh_CN.po与zh_CN.mo解压至此目录;
  4. 修改网站根目录下的wp-config.php文件:
    将 define(’DB_CHARSET’, ‘utf8‘); 修改为 define(’DB_CHARSET’, ”); ;
    将 define (’WPLANG’, ”); 修改为 define (’WPLANG’, ‘zh_CN’);

现在可以去刷新一下你的页面,试一下效果吧!

Position属性四个值:static、fixed、absolute和relative的区别和用法

在用CSS+DIV进行布局的时候,一直对position的四个属性值relative,absolute,static,fixed分的不是很清楚,以致经常会出现让人很郁闷的结果。今天研究了一下,总算有所了解。在此总结一下:

先看下各个属性值的定义:

1、static(静态定位):默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。

2、relative(相对定位):生成相对定位的元素,通过top,bottom,left,right的设置相对于其正常(原先本身)位置进行定位。可通过z-index进行层次分级。

3、absolute(绝对定位):生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。可通过z-index进行层次分级。

4、fixed(固定定位):生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 “left”, “top”, “right” 以及 “bottom” 属性进行规定。可通过z-index进行层次分级。

  • 定位属性详解

    1、relative

含义:定位为relative的元素脱离正常的文本流中,但其在文本流中的位置依然存在。

  他是默认参照父级的原始点为原始点,无父级则以文本流的顺序在上一个元素的底部为原始点,配合TRBL进行定位,当父级内有padding等CSS属性时,当前级的原始点则参照父级内容区的原始点进行定位,有以下属性:

  ① 如果没有TRBL,以父级的左上角,在没有父级的时候,他是参照浏览器左上角(到这里和absolute第一条一样),如果在没有父级元素的情况下,存在文本,则以文本的底部为原始点进行定位并将文字断开(和absolut不同)。

  ② 如果设定TRBL,并且父级没有设定position属性,仍旧以父级的左上角为原点进行定位(和absolut不同)。

  ③ 如果设定TRBL,并且父级设定position属性(无论是absolute还是relative),则以父级的左上角为原点进行定位,位置 由TRBL决定(前半段和absolute一样)。如果父级有Padding属性,那么就以内容区域的左上角为原点,进行定位(后半段和absolut不 同)。

  以上三点可以总结出,无论父级存在不存在,无论有没有TRBL,均是以父级的左上角进行定位,但是父级的Padding属性会对其影响。

综合上面对relative的叙述,我们就可以将position属性为relative的DIV视成可以用TRBL进行定位的的普通DIV,或者说 只要将我们平时布局页面的div的CSS属性中加上position:relative后,就不只是用float布局页面了,还可以用TRBL进行布局页 面 了,或者说加上position:relative的DIV也可以像普通的DIV进行布局页面了,只不过还可以用TRBL进行布局页面。但是 position属性为absolute不可以用来布局页面,因为如果用来布局的话,所有的DIV都相对于浏览器的左上角定位了,所以只能用于将某个元素 定位于属性为absolute的元素的内部某个位置。

Top的值表示对象相对原位置向下偏移的距离,bottom的值表示对象相对原位置向上偏移的距离,两者同时存在时,只有Top起作用。

left的值表示对象相对原位置向右偏移的距离,right的值表示对象相对原位置向左偏移的距离,两者同时存在时,只有left起作用。

如图1:

黄色背景的层定位为relative,红色边框区域为其在正常流中的位置。在通过top、left对其定位后,从灰色背景层的位置可以看出其正常位置依然存在

2、absolute
定位为absolute的层脱离正常文本流,但与relative的区别是其在正常流中的位置不再存在

这个属性总是有人给出误导。说当position属性设为absolute后,总是按照浏览器窗口来进行定位的,这其实是错误的。实际上,这是fixed属性的特点。

① 如果没有TRBL(top、right、bottom、left),以父级的左上角,在没有父级的时候,他是参照浏览器左上角,如果在没有父级元素的情况下,存在文本,则以它前面的最后一个文字的右上角为原点进行定位但是不断开文字,覆盖于上方。

② 如果设定TRBL,并且父级没有设定position属性,那么当前的absolute则以浏览器左上角为原始点进行定位,位置将由TRBL决定。

③ 如果设定TRBL,并且父级设定position属性(无论是absolute还是relative),则以父级的左上角为原点进行定位,位置由 TRBL决定。即使父级有Padding属性,对其也不起作用,说简单点就是:它只坚持一点,就以父级左上角为原点进行定位,父级的padding对其根 本没有影响。

以上三点可以总结出,若想把一个定位属性为absolute的元素定位于其父级元素内,只有满足两个条件:
第一:设定TRBL

第二:父级设定Position属性
上面的这个总结非常重要,可以保证你在用absolue布局页面的时候,不会错位,并且随着浏览器的大小或者显示器分辨率的大小,而不发生改变。


初学者很容易犯错的是,不清楚Position属性为absolute的板块,若想定位到父级板块中,并且当浏览器的大小改变或显示器的分辨率改变,布局不发生改变,是需要满足两个条件的,只要有一点不满足,元素就会以浏览器左上角为原点,从而导致页面布局错

位。
  Top的值表示对象上边框与浏览器窗口顶部的距离,bottom的值表示对象下边框与浏览器窗口底部的距离,两者同时存在时,只有Top起作用;如果两者都未指定,则其顶端将与原文档流位置一致,即垂直保持位置不变。

  left的值表示对象左边框与浏览器窗口左边的距离,right的值表示对象右边框与浏览器窗口右边的距离,两者同时存在时,只有left起作用;如果两者都未指定,则其左边将与原文档流位置一致,即水平保持位置不变。

在Position属性值为absolute的同时,如果有一级父对象(无论是父对象还是祖父对象,或者再高的辈分,一样)的Position属性值为Relative时,则上述的相对浏览器窗口定位将会变成相对父对象定位,这对精确定位是很有帮助的。

3、relative与absolute的主要区别
首先,是上面已经提到过的在正常流中的位置存在与否。
其次,relative定位的层总是相对于其最近的父元素,无论其父元素是何种定位方式。如图3:

图中,红色背景层为relative定位,其直接父元素绿色背景层为默认的static定位。红色背景层的位置为相对绿色背景层top、left个20元素。而如果红色背景层定位为absolute,则情形如下:

可 以看到,红色背景层依然定义top:20px;left:20px;但其相对的元素变为定位方式为absolute或relative的黄色背景层。因 此,对于absolute定位的层总是相对于其最近的定义为absolute或relative的父层,而这个父层并不一定是其直接父层。如果其父层中都未定义absolute或relative,则其将相对body进行定位,如图:

除top、left、right、bottom定位外,margin属性值的定义也符合上述规则。

总结:
属性为relative的元素可以用来布局页面,属性为absolute的元素用来定位某元素在父级中的位置,既然属性为absolute的元素用来定位某元素在父级中位置,就少不了TRBL,这时候根据一开始讲的absolute的第三条,如果父级元素没有position属性那么 absolute元素就会脱离父级元素,但是如果是布局页面,父级元素position的属性又不能为absolute,不然就会以浏览器左上角为原点 了,所以父级元素的position属性只能为relative!
如果用position来布局页面,父级元素的position属性必须为relative,而定位于父级内部某个位置的元素,最好用 absolute,因为它不受父级元素的padding的属性影响,当然你也可以用position,不过到时候计算的时候不要忘记padding的值

Note : 绝对(absolute)定位对象在可视区域之外会导致滚动条出现。而放置相对(relative)定位对象在可视区域之外,滚动条不会出现。

什么是文档流?
将窗体自上而下分成一行行, 并在每行中按从左至右的顺序排放元素,即为文档流。
只有三种情况会使得元素脱离文档流,分别是:浮动绝对定位和相对定位。

z-index属性
z-index,又称为对象的层叠顺序,它用一个整数来定义堆叠的层次,整数值越大,则被层叠在越上面,当然这是指同级元素间的堆叠,如果两个对象的此属 性具有同样的值,那么将依据它们在HTML文档中流的顺序层叠,写在后面的将会覆盖前面的。需要注意的是,父子关系是无法用z-index来设定上下关系 的,一定是子级在上父级在下。
Note:使用static 定位或无position定位的元素z-index属性是无效的。

Android导航组件Navigation

相关类图

一、前言

传统应用开发,一般都是采用一个界面一个Activity的形式,但是大家都知道,Activity在Android中是属于重量级的组件,从而导致程序资源消耗大,用户体验不佳。而导航组件Navigation采用的是Fragment轻量级的组件实现的,可以节省资源,提高用户体验。

二、导航简介

导航组件是Android Jetpack的一部分,主要用途是实现用户导航、进入和退出应用中不同内容片段的交互,不论是普通按钮点击,还是应用栏、抽屉导航栏等复杂的模式,它都能轻松应对,当然,导航组件也有它既定的导航原则来确保一致且可预测的用户体验。

2.1 导航组件的组成

导航组件主要有三部分组成:

  • 导航图(NavGraph): 这是包含所有导航相关信息的XML资源,这些信息包括应用内所有内容区域个体(称为目标,一般都是Fragment),以及用户可以通过应用跳转的可能路径。
  • 导航宿主(NavHost):这是用来显示导航图中声明的目标的一个空白容器。导航组件包含一个默认的NavHost实现(NavHostFragment),可用来显示Fragment目标。
  • 导航控制器(NavController):在NavHost中管理应用导航的目标,当用户在应用中进行操作时,导航控制器会控制目标的切换。

使用导航组件有各种优势,包括以下方面:

  • 自动处理Fragment事务
  • 在默认情况下,能够正确处理往返操作
  • 支持动画和转场动画
  • 支持导航界面模式(例如:抽屉式导航栏和底部导航栏)
  • Safe Args支持(一种可在导航和目标之间传递数据时,提供类型安全的Gradle插件)
  • ViewModel支持
  • 可以使用Android Studio的Navigation Editor来编辑和查看导航图(必须使用Android Studio 3.3及以上版本)

2.2 导航的原则

在使用导航组件时,应当遵循一些原则,以提高用户体验

注意:即使您为在项目中使用Navigation组件,您的应用也应遵循这些设计原则

2.2.1 固定的起始目的地

顾名思义,您构建的应用必须有一个固定的起始目的地,这个起始目的地就是指当应用启动时显示的第一个屏幕。起始目的地也是用户按返回按钮后,在回到启动器前看到的最后一个屏幕。

以上示例中,用户登录页面就是起始目的地,点击启动器图标打开应用,第一个启动的页面就是用户登录页面,在返回过程中,最后一个呈现的页面也是登录页面。

2.2.2 导航状态表现为目的地堆栈

在用户启动应用时,系统会启动一个新任务,并且显示起始目的地,这个起始目的地是应用导航的基础。当用户在应用中进行导航时,栈顶的目标就是显示在屏幕上的,而栈内的所有目标都是历史记录。

导航组件会为你管理所有返回栈的顺序,当然你也可以自行管理,已达到某些目的。

2.2.3 在应用的任务中向上按钮和返回按钮行为相同

首先,说下什么事向上按钮,向上按钮是指在应用中的返回上一级的按钮(一般是在用户导航栏中),返回按钮则是系统导航中的返回按钮。在应用的任务中,向上按钮和返回按钮的行为相同,都是将栈顶的目标移除,返回到上一个目标。

2.2.4 向上按钮不会退出应用

在应用的任务中,向上按钮可以返回到上一个目标,但是绝不会退出应用

2.2.5 深度链接可以模拟手动导航

无论是通过深度链接至特定的目的地,还是手动导航到特定目的地,都可以使用向上按钮通过各个目的地导航回到起始目的地。当深度链接至特定的目的地时,会移除所有返回栈中的任务,并替换为深度链接的返回栈。值得注意的是,深度链接合成的返回栈是一个完整的返回栈,他跟手动导航至特定目的地具有相同的返回栈,这个是非常重要的,因为合成的返回栈必须是真实的。

三、 使用入门

3.1 添加依赖

在应用模块目录下的build.gradle文件中添加dependencies依赖声明:

dependencies {
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'

    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
    implementation 'androidx.navigation:navigation-dynamic-features-fragment:2.3.5'

    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    androidTestImplementation 'androidx.navigation:navigation-testing:2.3.5'
}

3.2 创建导航图资源文件

导航是发生在各个目的地之间的,而这些目的地通过操作连接在一起。导航图是一种资源文件,它包含了所有的目的地和操作的声明。

创建导航图资源文件,可以按一下不走进行:

  1. 在项目程序模块下的res目录下,右键-》New-》Android Resource File
  2. 在弹出的窗口中输入文件名
  3. 在Resource type中选择Navigation
  4. 点击确定创建资源文件

新建的导航图资源文件时一个XML资源文件,以navigation为根节点,大致内容如下:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph">

</navigation>

<navigation>元素是导航图的根元素。当你向图表添加目的地和连接操作时,可以看到相应的<destination>和<action>子元素。如果有嵌套图表,也会出现<navigation>子元素。

如果是首次添加导航图资源,Android Studio会在res目录内创建一个navigation资源目录,该目录包含你的导航图资源文件。当然,如果你已经够熟练,可以直接创建目录和文件的方式创建。

3.2.1 Android Studio中的Navigation Editor

Android Studio提供了强大的导航编辑器,在这里不但可以预览你所添加的目标,还可以修改导航图,可以通过拖动的方式或者直接编码修改底层XML的方式修改导航图。为了方便项目的维护和代码可读性,但更加建议使用修改底层XML的方式,或者结合修改底层XML的方式。

温馨提示:不同版本的Android Studio的界面操作有些不一样,不少从旧版本升级到3.6之后,发现打开资源文件的时候,默认是Design模式(包括layout布局资源),一时间找不到北了,不知道如何切换成修改底层XML的模式。其实这是3.6版本之后的小改动,在旧版本只有code和design两种模式,新版本code、split、design三种模式,而且模式切换位置也变了,旧版本是在左下角,而新版本是在右上角(如下图)

3.3 向Activity添加导航宿主(HavHost)

导航宿主是导航组件的核心部分之一,导航宿主是一个空容器,用来存放和处理目的地。导航宿主必须派生于NavHost,NavHostFragment是导航组件的默认宿主实现,负责处理Fragment目的地的交互。

注意:导航组件的设计理念是用于具有一个主Activity和多个Fragment目的地的应用,主Activity与导航图相关联,并且包含一个负责根据需要交换目的地的HavHostFragment。如果你的应用需要再多个Activity上实现导航,就需要为每个Activity添加导航宿主,并在每个Activity关联其自己的导航图。

3.3.1 通过XML添加NavHostFragment

在主Activity的布局文件中,添加<fragment>标签,并在内部指定导航图,如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="?attr/actionBarSize">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" />

    <fragment
        android:id="@+id/nav_host_fragment_activity_main"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

详细解说:

  • android:name属性包含NavHost实现类的名称(示例中使用的是默认实现HAVHostFragment,如果有需要,可以使用自定义的Fragment类,但是必须实现HavHost或者继承NavHostFragment)
  • app:navGraph属性将导航宿主(NavHostFragment)与导航图关联,指向包含所有导航目的地的导航图资源文件
  • app:defaultNavHost=”true” 属性确保导航宿主会拦截系统返回按钮。请注意,只能有一个默认导航宿主,如果同一布局中有多个导航宿主,请务必仅指定一个默认导航宿主。

说明:导航组件是Android Jetpack的一部分,不属于Android系统组件,所以需要在布局中添加属性引入,如:xmlns:app="http://schemas.android.com/apk/res-auto"

除此之外,还可以使用Layout Editor向Activity添加导航宿主,具体步骤如下:

  1. 打开Activity布局文件,切换到Design窗口
  2. 在Palette窗口选择Containers,然后找到NavHostFragment(可直接搜索)
  3. 将NavHostFragment拖向布局中
  4. 在弹出的窗口中选择导航图,然后确定
  5. 在Properties窗口设置相关属性

3.3.2 向导航图中添加目的地

对于不想编码的小伙伴可以使用Navigation Editor向导航中添加目的地,因为这些都是用户引导模式的,没什么可说,在这里主要讲一下手动添加目的地的步骤:

  1. 新建Fragment类和布局文件,并实现相关逻辑代码
  2. 在导航图XML中新增<fragment>标签
  3. 配置<fragment>标签的相关属性,如:android:id、android:name、android:label等
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/navigation_home">

    <fragment
        android:id="@+id/navigation_home"
        android:name="com.shijiusui.p.ppjoke.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/navigation_dashboard"
        android:name="com.shijiusui.p.ppjoke.ui.dashboard.DashboardFragment"
        android:label="@string/title_dashboard"
        tools:layout="@layout/fragment_dashboard" />

    <fragment
        android:id="@+id/navigation_notifications"
        android:name="com.shijiusui.p.ppjoke.ui.notifications.NotificationsFragment"
        android:label="@string/title_notifications"
        tools:layout="@layout/fragment_notifications" />
</navigation>

目的地属性详解

  • Type:即标签名称,指示在源代码中,该目的地是作为Fragment、Activity还是其他自定义实现的
  • android:label:这个属性指定目的地的名称
  • android:id:这个属性指定该目的地的ID,用于在代码中引用该目的地
  • android:name:这个属性用来指定目的地所关联的类

除此之外,还可以通过tools:layout属性指定预览布局文件,这样就可以在导航图编辑中看到对应的布局预览了。

3.3.3 将某个目的地指定为起始目的地

导航的原则之一就是固定的起始目的地,指定起始目的地的方法有两种:

一种是使用Navigation Editor,在Design窗口中,选中需要指定为起始目的地的目标,点击“房子”图标(如下图)即可。

另一种方法就是在XML源代码中,在<navigation>标签中添加app:startDestination属性进行指定,属性值为需要指定的目的地的ID(如下示例)

示例:通过XML代码指定起始目的地

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/navigation_home">

    <fragment
        android:id="@+id/navigation_home"
        android:name="com.shijiusui.p.ppjoke.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/navigation_dashboard"
        android:name="com.shijiusui.p.ppjoke.ui.dashboard.DashboardFragment"
        android:label="@string/title_dashboard"
        tools:layout="@layout/fragment_dashboard" />

    <fragment
        android:id="@+id/navigation_notifications"
        android:name="com.shijiusui.p.ppjoke.ui.notifications.NotificationsFragment"
        android:label="@string/title_notifications"
        tools:layout="@layout/fragment_notifications" />
</navigation>

3.3.4 连接目的地

目的地之间的逻辑连接也叫做操作,操作一般是将一个目的地连接到另一个目的地,当然,你也可以定义全局操作,这类操作可以在任意位置跳转到指定目的地,这个在后面详细讲到。

我们可以使用Navigation Editor连接两个目的地,直接拖动箭头即可,在这里就不多介绍这种方式,直接介绍通过修改XML源码的方式(其实使用Navigation Editor也会自动修改XML源码),具体步骤如下:

  1. 在<fragment>标签内部新增<action>标签
  2. 配置android:id和app:destnation属性
  3. 如果需要,可以配置app:enterAnim、app:exitAnim、app:popEnterAnim、app:popExitAnim属性定义动画

详细解说:

  • Type:即<action>标签
  • android:id:这个字段是操作ID,代码中通过这个ID执行操作
  • app:destnation:这个字段是操作的目的地,用来指定操作跳转的目的地
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/navigation_home">

    <fragment
        android:id="@+id/navigation_home"
        android:name="com.shijiusui.p.ppjoke.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_homeFragment_to_dashboard"
            app:destination="@+id/navigation_dashboard"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />
    </fragment>

    <fragment
        android:id="@+id/navigation_dashboard"
        android:name="com.shijiusui.p.ppjoke.ui.dashboard.DashboardFragment"
        android:label="@string/title_dashboard"
        tools:layout="@layout/fragment_dashboard" />

    <fragment
        android:id="@+id/navigation_notifications"
        android:name="com.shijiusui.p.ppjoke.ui.notifications.NotificationsFragment"
        android:label="@string/title_notifications"
        tools:layout="@layout/fragment_notifications" />
</navigation>

 

3.3.5 导航到目的地

完成了导航图的各种配置,那么就需要在代码中实现导航到目的地了。导航到目的地是使用NavController完成,这是在导航宿主中管理导航的对象的,每个导航宿主都有自己的相应导航控制器(NavController)。导航到目的地的步骤如下:

  1. 检索导航控制器
  2. 导航到目的地

3.3.5.1 检索导航控制器

检索导航宿主的导航控制器,可以通过以下方法:

Kotlin:

  • Fragment.findNavController()
  • View.findNavController()
  • Activity.findNavController(viewId: int)

Java:

  • NavHostFragment.findNavController(Fragment)
  • Navigation.findNavController(Activity, int viewId)
  • Navigation.findNavController(View)

说明:Kotlin可以直接在Fragment、View以及Activity使用findNavController是因为使用了扩展方法,当然,也可以直接跟Java那样调用对应的接口。

class HomeFragment : Fragment() {

    private lateinit var homeViewModel: HomeViewModel
    private var _binding: FragmentHomeBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        homeViewModel =
            ViewModelProvider(this).get(HomeViewModel::class.java)

        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        val root: View = binding.root

        val textView: TextView = binding.textHome
        homeViewModel.text.observe(viewLifecycleOwner, Observer {
            textView.text = it
        })

        textView.setOnClickListener { 
            val navController = findNavController()
        }
        
        return root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

3.3.5.2 导航到目的地

检索到导航控制器之后,使用导航控制器类的NavController.navigation() API导航到指定的目的地,NavController.navigation()有多个变体,这里就以使用目的地ID进行导航为例:

class HomeFragment : Fragment() {

    private lateinit var homeViewModel: HomeViewModel
    private var _binding: FragmentHomeBinding? = null

    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        homeViewModel =
            ViewModelProvider(this).get(HomeViewModel::class.java)

        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        val root: View = binding.root

        val textView: TextView = binding.textHome
        homeViewModel.text.observe(viewLifecycleOwner, Observer {
            textView.text = it
        })

        textView.setOnClickListener { 
            val navController = findNavController()
            navController.navigate(R.id.action_homeFragment_to_dashboard)
        }
        
        return root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

3.3.5.3 返回到指定目的地

返回到指定目的地,是指返回到之前导航过的目的地,这些目的地必须是在任务栈内的,可以通过NavController.popBackStack()接口返回上一级,或者通过NavController.popBackStack(int destinationId, boolean inclusive)返回到指定某个目的地。

到这里,已经基本掌握了导航组件的使用,后续的将会进行更加深入地介绍导航组件的使用。

四、进阶之路

4.1 创建不同类型的导航目的地

在导航图中,导航目的地不局限与Fragment,其实还可以是Activity、DialogFragment甚至前台的导航图navigation(即<navigation>内部再嵌套一个<navigation>)。详细的添加导航目的地的方法已经在3.3.2 向导航图中添加目的地详细说明了,这里就不再累赘。

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/navigation_home">

    <fragment
        android:id="@+id/navigation_home"
        android:name="com.shijiusui.p.ppjoke.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_homeFragment_to_dashboard"
            app:destination="@+id/navigation_dashboard"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />
    </fragment>
    
    <dialog 
        android:id="@+id/tips"
        ...
        />
    <activity 
        android:id="@+id/xxxx"
        ...
        />
</navigation>

注意:

  1. 如果目的地是Activity类型,转场动画必须结合Activity处理,仅仅在导航连接目的地中声明动画,弹出动画将达不到预期的效果(参考:将弹出动画应用与Activity目的地过度)
  2. 如果目的地是Activity类型,实际上就是已经离开了当前的导航组件范围
  3. 如果使用<dialog>声明导航目的地,必须是DialogFragment类型(包括其子类)

4.2 嵌套导航图

所谓的嵌套导航图,就是在导航图内再嵌入一个导航图,外部的称为父导航图,内部的叫子导航图。嵌套的导航图封装着自己的目的地,且必须标识起始目的地,父导航图访问子导航图只能通过子导航图的起始目的地(不能直接访问子导航图中的目的地),因为子导航图拥有不一样的导航控制器(NavController)。使用嵌套导航图可以对导航目的地进行分类封装,防止错误的访问。

嵌套导航图有两种表现形式,一种是在导航图<navigation>标签内部嵌套一个<navigation>标签;另一种是使用include标签引入导航图资源文件。

注意事项:

  1. 两种表现形式效果是一样的,如果导航图比较复杂,使用第二种会使得导航图资源显得更加简洁
  2. 父导航图中访问子导航图,不能直接访问子导航图中的目的地,只能通过子导航图ID访问子导航图的起始目的地

4.2.1 在导航图<navigation>标签内部嵌套一个<navigation>标签

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/navigation_home">

    <navigation
        android:id="@+id/Settings"
        app:startDestination="@id/settingsFragment">
        ....
    </navigation>
</navigation>

4.2.2 使用include标签引入导航图资源文件

  • 定义一个导航图资源文件(nav_graph_settings.xml)
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/settingsFragment">

    ...
    
</navigation>
  • 在主导航图中使用导航图资源
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/navigation_home">
    
    <include app:graph="@navigation/nav_graph_settings" />

</navigation>

4.3 全局操作

操作就是目的地之间的跳转(<action>),全局操作就是指在导航图内所有目的地都能执行的操作。通常我们在目的地标签内部声明操作(<action>),但是全局操作是在导航图内声明(<navigation>标签下)。全局操作的使用跟普通的操作一样,不同的是它可以在当前导航图下所有的目的地内都可以使用。

注意事项:

  1. 全局操作只能再同一导航图内的目的地中调用,不可在所声明的导航图外部使用,即使是子导航图中的目的地也不允许
  2. 全局操作的目的地必须是当前导航图下的目的地或子导航图入口(子导航图中的目的地也是不允许的)
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph">

    <!-- 
        全局操作,在navigation标签下声明
        目的地必须是当前导航图下的目的地或子导航图入口
     -->
    <action android:id="@+id/action_to_settings_more"
        app:destination="@+id/commonFragment"
        ...
        />

    <fragment
        android:id="@+id/commonFragment"
        ...
        />

</navigation>

4.4 在目的地之间传递数据

导航支持你通过定义目的地参数传递数据附加到导航操作,在不同目的地之间实现数据传递。

提示:建议仅在目的地之间传递最少量的数据,因为在Android上用于保存所有状态的总空间是有限的,如果需要传递大量数据,可以考虑其他替代方案。

4.4.1 定义参数

在导航图的操作中可以定义参数,在操作中定义的参数,有几个属性:

  • android:name: 参数名
  • android:defaultValue: 参数默认值
  • app:argType: 参数类型
  • app:nullable: 是否可为空
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph_login"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/loginFragment"
        android:name="com.shijiusui.p.ppjoke.navigationdemo.LoginFragment"
        android:label="LoginFragment"
        tools:layout="@layout/fragment_login">
        <action
            android:id="@+id/action_loginFragment_to_registerFragment"
            app:destination="@+id/registerFragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right"/>
        <argument
            android:name="type"
            app:argType="integer"
            android:defaultValue="0"
            app:nullable="false"/>
        <argument
            android:name="uname"
            app:argType="string"
            android:defaultValue="@null"
            app:nullable="true"/>

        <deepLink
            android:id="@+id/settingsDeepLink"
            app:uri="https://blog.shijiusui.com/?type={type}&amp;uname={uname}"
            android:autoVerify="false"/>
    </fragment>

</navigation>

导航库支持的参数类型包括:

类型 app:argType语法 是否支持默认值 是否支持null值
整数 app:argType=”integer”
浮点数 app:argType=”float”
长整数 app:argType=”long”
默认值必须始终以“L”后缀结尾
例如:“123L”
布尔值 app:argType=”boolean”
“true”或“false”
字符串 app:argType=”string”
资源引用 app:argType=”reference”
默认值必须为“@resourceType/resourceName”格式
例如:“@style/myCustomStyle”或“0”
自定义Parcelable app:argType=””
其中是Parcelable的完全限定类名称
支持默认值“@null”
不支持其他默认值
自定义Serializable app:argType=””
其中是Serializable的完全限定类名称
支持默认值“@null”
不支持其他默认值
自定义Enum app:argType=””
其中是Enum的完全限定名称

默认值必须与非限定名称匹配
例如:“SUCCESS”匹配MyEnum.SUCCESS

注意:

  1. 如果参数传递的是Parcelable、Serializable和Enum时,注意所传递的参数类型的类在代码混淆时需要做排除处理
  2. <argument>可以在<fragment>和<action>标签内,如果需要通过深层链接传参,务必配置在<fragment>标签内声明(更多信息参考:创建隐式深层链接)

4.4.2 在导航时传递参数

在导航时,也可以实现在目的地之间传递参数,只需要调用带有参数传递的NaviController.navigate()接口即可。

val navController = findNavController()
navController.navigate(R.id.action_homeFragment_to_loginFragment, Bundle().also {
    it.putInt("type", 2)
})

注意事项:

  1. 在导航图的操作中定义的参数,参数值是固定的,但是这个值可以被导航时传递的参数覆盖
  2. 导航的参数传递是单向的,无法实现往回传递,如果需要往回传递参数,可以通过目的地所有者Activity进行

4.5 在目的地之间添加动画效果

导航支持在目的地之间添加动画,以提高用户体验。导航动画在定义操作是添加,动画包括以下类型:

  • app:enterAnim: 进入目的地的动画(新的目的地进入的动画)
  • app:exitAnim: 退出目的地的动画(新的目的地进入,旧目的地退出的动画)
  • app:popEnterAnim: 通过弹出操作进入的目的地的动画(弹出操作时,进入的目的地进入的动画)
  • app:popExitAnim:通过弹出操作退出的目的地的动画(弹出操作时,退出的目的地退出的动画)
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph_login"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.shijiusui.p.ppjoke.navigationdemo.HomeFragment"
        android:label="HomeFragment"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_homeFragment_to_loginFragment"
            app:destination="@+id/loginFragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right" >
        </action>
    </fragment>

4.5.1 将弹出动画应用于Activity目的地过度

当导航的目的地是Activity类型时,,在操作添加的过度动画,会出现进入(打开Activity)时动画正常,但是返回时(从Activity中返回)的动画却失效了。针对目的地是Activity类型时,需要对弹出动画做特殊处理,你需要重写Activity的finish()方法,在内部调用ActivityNaviagtor.applyPopAnimationsToPendingTransition(Activity)接口即可。

    override fun finish() {
        super.finish()
        ActivityNavigator.applyPopAnimationsToPendingTransition(this)
    }

4.6 为目的地创建深层链接

在Android中,深层链接是指将用户直接转到应用内特定目的地的链接。借助导航组件,你可以创建两种不同类型的深层链接:显式深层链接和隐式深层链接。

4.6.1 创建显式深层链接

显式深层链接是深层链接的一个实例,该实例使用PendingIntent将用户转到应用内的特定位置(例如,可以在通知、应用快捷方式或应用微件中显式深层链接)。

当用户通过显式深层链接打开应用时,任务返回堆栈会被清除,并被替换为相应的深层链接目的地。当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个<navigation>元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,就像从应用入口一步步进入到指定目的地的返回一样的效果。

创建显式深层链接,可以使用NavDeepLinkBuilder类构建PendingIntent,如下示例:

val pendingIntent = NavDeepLinkBuilder(requireContext())
    .setGraph(R.navigation.nav_graph)
    .setDestination(R.id.accountSettingFragment)
    .createPendingIntent()

如果已有NavController,则还可以通过NavController.createDeepLink() API创建深层链接,如下所示:

val pendingIntent = findNavController()
    .createDeepLink()
    .setGraph(R.navigation.nav_graph)
    .setDestination(R.id.accountSettingFragment)
    .createPendingIntent()

注意事项:

  1. 第一种创建显式深层链接的方式中,如果提供的上下文不是Activity,构造函数会使用PackageManager.getLaunchIntentForPackage()作为默认Activity来启动(如果有)
  2. 显式深层链接生成的对象时PendingIntent,适合的场景有通知、快捷方式启动、桌面小部件等

显式深层链接的使用示例(在通知中使用):

private fun showNotification(context: Context) {
    var notificationManager:NotificationManager
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        notificationManager = context.getSystemService<NotificationManager>(NotificationManager::class.java)
        if (null != notificationManager) {
            val channel = NotificationChannel("default", "default", NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(channel)
        }
    } else {
        notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    }

    val pendingIntent = findNavController().createDeepLink()
        .setGraph(R.navigation.nav_graph)
        .setDestination(R.id.accountSettingFragment)
        .createPendingIntent()

    val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, "default")
        .setSmallIcon(R.mipmap.ic_launcher)
        .setContentTitle("测试深层链接")
        .setContentText("测试显示深层链接打开应用")
        .setContentIntent(pendingIntent) //                .setVibrate(new long[] { 1000, 1000, 1000, 1000, 1000 })
        .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
        .setAutoCancel(true)


    notificationManager.notify(1, builder.build())
}

4.6.2 创建隐式深层链接

隐式深层链接是指应用中特定目的地的URI。调用URI(例如用户点击某个链接)时,Android可以将应用打开并自动导航到相应的目的地。

当用户触发隐式深层链接时,返回堆栈的状态取决于是否使用Intent.FLAG_ACTIVITY_NEW_TASK标记启动隐式Intent:

  • 如果改标记已设置,则任务返回堆栈会被清除,并被替换为相应的深层链接指定的目的地。就像显示深层链接,当嵌套图表时,每个嵌套级别的起始目的地(即层次结构中每个<navigation>元素的起始目的地)也会添加到相应堆栈中。也就是说,当用户从深层链接目的地按下返回按钮时,就像从应用入口一步步进入到指定目的地的返回一样的效果
  • 如果该标记未设置,则仍然位于上一个应用的任务堆栈中,该应用中的隐式深层链接已触发。在这种情况下,如果按下返回按钮,则会返回到上一个应用;如果按下向上按钮,则会在导航图中的层次父级目的地上启动应用的任务。

创建隐式深层链接请按下面步骤:

第一步:在导航图中添加隐式深层链接声明

在导航图中添加隐式深层链接声明,只需要再导航图内目的地中添加<deepLink>标签,配置相关属性:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph_login"
    app:startDestination="@id/homeFragment">
    
    <fragment
        android:id="@+id/loginFragment"
        android:name="com.shijiusui.p.ppjoke.navigationdemo.LoginFragment"
        android:label="LoginFragment"
        tools:layout="@layout/fragment_login">
        <action
            android:id="@+id/action_loginFragment_to_registerFragment"
            app:destination="@+id/registerFragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right"/>
        <argument
            android:name="type"
            app:argType="integer"
            android:defaultValue="0"
            app:nullable="false"/>
        <argument
            android:name="uname"
            app:argType="string"
            android:defaultValue="@null"
            app:nullable="true"/>

        <deepLink
            android:id="@+id/settingsDeepLink"
            app:uri="https://blog.shijiusui.com/?type={type}&amp;uname={uname}"
            android:autoVerify="false"/>
    </fragment>

</navigation>

<deepLink>标签属性说明:

  • android:id:深层链接ID
  • app:uri:深层链接Uri
  • android:autoVerify:要求Google验证你是相应URI的所有者(可选),API 23开始有效

创建隐式深层链接需要注意的几点:

  • 没有协议的URI会假定为同时支持http和https。例如blog.shijiusui.com,会同时和http://blog.shijiusui.com与https://blog.shijiusui.com匹配
  • 深层链接的后缀中可以包含形式为{placeholder_name}的占位符,用来匹配一个或多个字符。例如,https://blog.shijiusui.com/users/{id}与https://blog.shijiusui.com/9匹配。导航件通过将占位符与深层链接所指向的目的地中已定义的参数相匹配,并尝试将占位符值解析为相应的类型。如果目的地中没有定义具有相同名称的参数,则使用默认的String类型传参数。
  • 在深层链接的后缀中,可以使用.*通配符匹配0个或多个字符
  • 如果目的地参数列表中定义了不能为null的参数,则深层链接必须包含该参数且不能为空,否则打开应用会有异常

第二步:启动导航图中的隐式深层链接

声明了隐式深层链接,接下来必须启用隐式深层链接,在应用的AndroidManifest.xml文件中,在导航图所关联的<activity>声明中添加<nav-graph>元素,如下所示:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <!-- 启用导航图中的隐式深层链接 -->
    <nav-graph android:value="@navigation/nav_graph" />
</activity>

构建项目时,导航件会将<nav-graph>元素进行转换生成<intent-filter>元素,以匹配导航图中的所有深层链接。

注意事项:

  1. 启用隐式深层链接必须在导航图关联的Activity声明中进行
  2. <nav-graph>元素在Android Studio 3.0或3.1中不支持,使用这些版本时,必须改为手动添加intent-filter元素

第三步:测试隐式深层链接

隐式深层链接是URI,可以编写一个含有深层链接跳转的html文件到存储中,使用浏览器访问该html文件,如下所示:

<!DOCTYPE html>
<head>
    <meta charset="UTF-8" />
    <meta id="viewport" 
          name="viewport" 
          content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,minimal-ui">
</head>
<html>
    <input type="button" 
           value="点击我打开Deeplink" 
           onclick="javascrtpt:window.location.href='https://blog.shijiusui.com/?type=1&uname=sjs'">
</html>

将以上文件存储到手机存储中,在浏览器的地址中输入file:///<html_path>(<html_path>为html文件再存储中的路径,例如:file:///sdcard/test_deeplink.html),访问html之后(如下图),点击按钮就可以打开应用并进入到相应的目的地。

注意事项:

  1. 如果深层链接中使用的协议头在其他应用中也声明了,打开深层链接系统时,可能会弹出应用选择列表,这个问题可以在定义深层链接时,通过定义应用独有的协议头规避
  2. 如果导航组件自动生成的intent-filter无法正确进入到目的地,请确认深层链接是否跟自动生成的intent-filter一致,如果不一致,可以修改深层链接或采用手动添加intent-filter(如果遇到了https://blog.shijiusui.com的深层链接,生成的intent-filter中包含了android:path=”/” 导致无法正确访问到指定目的地,修改深层链接为 https://blog.shijiusui.com/,就能解决)。

手动添加intent-filter元素示例:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <!-- 启用导航图中的隐式深层链接 -->
<!--    <nav-graph android:value="@navigation/nav_graph" />-->
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https"
            android:host="blog.shijiusui.com"
            android:path="/" />
    </intent-filter>
</activity>

总结:

导航组件有非常大的优势,不但使用轻量级Fragment,还能在使用深层链接打开应用时,能自动构建返回栈(如果是隐式深层链接,启动务必使用Intent.FLAG_ACTIVITY_NEW_TASK)。

P1-Race walking

Race walking shares many fitness benefits with running, research shows, while most likely contributing to fewer injuries. It does, however, have its own problem.

翻译
尽管很可能竞走对身体的损伤更少,但是研究表明,竞走和跑步一样对健身有很多好处,然而它确实也有自身的问题

Race walkers are conditioned athletes. The longest track and field event at the Summer Olympics is the 50-kilometer race walk, which is about five miles longer than the marathon. But the sport’s rules require that a race walker’s knees stay straight through most of the leg swing and one foot remain in contact with the ground at all times. It’s this strange form that makes race walking such an attractive activity, however, says Jaclyn Norberg, an assistant professor of exercise science at Salem State University in Salem, Mass.

翻译
竞走运动员是受规则限制的运动员。夏季奥运会上最长的田径比赛项目是50公里竞走,比马拉松长约5英里。但这项运动的规则要求竞走运动员在几乎整个摆腿过程中让膝盖保持笔直,并且需要一只脚一直与地面保持接触状态。不过,麻省塞勒姆州立大学运动科学助理教授贾克林·诺伯格说,正是这种奇特的形式使得竞走成为一项如此吸引人的活动。

Like running, race walking is physically demanding, she says, According to most calculations, race walkers moving at a pace of six miles per hour would burn about 800 calories per hour, which is approximately twice as many as they would burn walking, although fewer than running, which would probably burn about 1,000 or more calories per hour.

翻译
她说,和跑步一样,竞走也需要体能。根据大多数的计算结果,竞走运动员以每小时6英里的速度竞走,每小时大约会消耗800卡路里,尽管比跑步的消耗要少,但却大约是走路时卡路里消耗量的两倍,跑步可能每小时消耗1000卡路里或更多。

However, race walking does not pound the body as much as running does, Dr. Norberg says. According to her research, runners hit the ground with as much as four times their body weight per step, while race walkers, who do not leave the ground, create only about 1.4 times their body weight with each step.

As a result, she says, some of the injuries associated with running, such as runner’s knee, are uncommon among race walkers. But the sport’s strange form does place considerable stress on the ankles and hips, so people with a history of such injuries might want to be cautious in adopting the sport. In fact, anyone wishing to try race walking should probably first consult a coach or experienced racer to learn proper technique, she says. It takes some practice.

继续阅读P1-Race walking

摄像头预览 三种方式

摄像头预览有3种方式:

  1. setPreviewDisplay(holder)
  2. setPreviewTexture(surfaceTexture)
  3. 自定义

第一种会给摄像头绑定一个视窗(最终的型态是一个Surface,Egl会对转换成Native的Window与之对应),摄像头采集完数据,再绘制到视窗上,这都是操作系统自发处理的。
第二种是输出到纹理,这个纹理可以做一些处理,加上其他一些OpenGL的操作,最终输出到视窗上。
第三种,也可以使用自定义的方式通过PreviewCallback拿到Frame数据,装换到2D纹理,再输出到视窗。
后两者是可以进行自定义的OpenGL渲染的,比如特效,滤镜等。

Android 推流–camera后置切前置不生效

七牛android端推流的摄像头id (CAMERA_FACING_ID) 目前一共有三种模式:

CAMERA_FACING_ID.CAMERA_FACING_FRONT ==> 前置摄像头

CAMERA_FACING_ID.CAMERA_FACING_BACK ==> 后置摄像头

CAMERA_FACING_ID.CAMERA_FACING_3RD ==> 后置副摄像头,即双摄像头的副摄像头

目前开发者会遇到某些机型,后置摄像头切换之前不成功的情况,这一类问题确认下来是

该机型只有后置摄像头(需要前置的话,直接翻转摄像头)
eg. 华为荣耀7i
所以可以针对这类机型,在调用camera切换的API的时候,给予用户一个提示信息即可。

Android高版本联网失败报错:Cleartext HTTP traffic to xxx not permitted解决方法

2020-09-08 18:29:53.519 18200-18200/com.shijiusui.p.screenlive I/Glide: Root cause (1 of 1)
     java.io.IOException: Cleartext HTTP traffic to images.cdn.xxxxxx.com not permitted
         at com.android.okhttp.HttpHandler$CleartextURLFilter.checkURLPermitted(HttpHandler.java:115)
         at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:458)
         at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:127)
         at com.bumptech.glide.load.data.HttpUrlFetcher.loadDataWithRedirects(HttpUrlFetcher.java:104)
         at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:59)
         at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.loadData(MultiModelLoader.java:100)
         at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.startNextOrFail(MultiModelLoader.java:164)
         at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.onLoadFailed(MultiModelLoader.java:154)
         at com.bumptech.glide.load.data.HttpUrlFetcher.loadData(HttpUrlFetcher.java:65)
         at com.bumptech.glide.load.model.MultiModelLoader$MultiFetcher.loadData(MultiModelLoader.java:100)
         at com.bumptech.glide.load.engine.SourceGenerator.startNext(SourceGenerator.java:62)
         at com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:309)
         at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:279)
         at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:235)
         at java.util.concurrent.ThreadPoolExecutor.processTask(ThreadPoolExecutor.java:1187)
         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
         at java.lang.Thread.run(Thread.java:784)

前言:为保证用户数据和设备的安全,Google针对下一代 Android 系统(Android P) 的应用程序,将要求默认使用加密连接,这意味着 Android P 将禁止 App 使用所有未加密的连接,因此运行 Android P 系统的安卓设备无论是接收或者发送流量,未来都不能明码传输,需要使用下一代(Transport Layer Security)传输层安全协议,而 Android Nougat 和 Oreo 则不受影响。

因此在Android P 使用HttpUrlConnection进行http请求会出现以下异常

W/System.err: java.io.IOException: Cleartext HTTP traffic to **** not permitted

使用OKHttp请求则出现

java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security policy

在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,同样地,如果应用嵌套了webview,webview也只能使用https请求。

针对这个问题,有以下三种解决方法:

(1)APP改用https请求

(2)targetSdkVersion 降到27以下

(3)更改网络安全配置

前面两个方法容易理解和实现,具体说说第三种方法,更改网络安全配置。

1.在res文件夹下创建一个xml文件夹,然后创建一个network_security_config.xml文件,文件内容如下:

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <network-security-config>
  3. <base-config cleartextTrafficPermitted=”true” />
  4. </network-security-config>

2.接着,在AndroidManifest.xml文件下的application标签增加以下属性:

  1. <application
  2. android:networkSecurityConfig=”@xml/network_security_config”
  3. />

完成,这个时候App就可以访问网络了。

方法四:在AndroidManifest.xml配置文件的<application>标签中直接插入

android:usesCleartextTraffic=”true”

error=86, Bad CPU type in executable

最近在维护一个N久的项目时,发现在mac升级系统为10.15.5后(Android Studio 4.0,gradle 2.3.1),编译失败了,报错如下:

Cannot run program “/Users/xxxx/Android/sdk/build-tools/23.0.1/aapt”: error=86, Bad CPU type in executable

原因是最新版本的macOS Catalina(10.15.5)已经不支持32位的应用了,只能运行64位的应用。

解决方法:升级工程的buildToolsVersion,本例中将23.0.1 升级成25.0.3

leakcanary内存泄露检测工具报错 Dumping memory, app will freeze. Brrr

报错信息:Dumping memory, app will freeze. Brrr

点击查看leaks小黄标,看到报错信息:
java.lang.UnsupportedOperationException: Could not find char array in java.lang.String@334750520 (0x13f3e338)
at com.squareup.leakcanary.HahaHelper.asString(HahaHelper.java:108)
at com.squareup.leakcanary.HeapAnalyzer.findLeakingReference(HeapAnalyzer.java:161)
at com.squareup.leakcanary.HeapAnalyzer.checkForLeak(HeapAnalyzer.java:115)
at com.squareup.leakcanary.internal.HeapAnalyzerService.onHandleIntent(HeapAnalyzerService.java:58)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:76)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.os.HandlerThread.run(HandlerThread.java:65)

查看我本地的集成信息:

debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'

原因是我leakcanary集成版本过低,推荐使用1.5.4或者1.6版本

debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6'
testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6'

修改以后重新运行,一切OK,能成功检测到内存泄漏。

SSH上传本地文件到linux服务器

在linux下一般用scp这个命令来通过ssh传输文件。

1、从服务器上下载文件

scp username@servername:/path/filename /var/www/local_dir(本地目录)

 例如scp root@192.168.0.101:/var/www/test.txt  #把192.168.0.101上的/var/www/test.txt 的文件下载到/var/www/local_dir(本地目录)

2、上传本地文件到服务器

scp /path/filename username@servername:/path

例如scp /var/www/test.php root@192.168.0.101:/var/www/  #把本机/var/www/目录下的test.php文件上传到192.168.0.101这台服务器上的/var/www/目录中

3、从服务器下载整个目录

scp -r username@servername:/var/www/remote_dir/(远程目录) /var/www/local_dir(本地目录)

例如:scp -r root@192.168.0.101:/var/www/test  /var/www/  

4、上传目录到服务器

scp  -r local_dir username@servername:remote_dir

例如:scp -r test  root@192.168.0.101:/var/www/   #把当前目录下的test目录上传到服务器的/var/www/ 目录

注:目标服务器要开启写入权限。

配置 Docker + gitlab-runner 实现线上自动编译

Last login: Mon Apr 20 15:44:24 on ttys002
xxxs-MacBook-Pro:~ kenny$ ssh -l kenny 10.211.55.3
kenny@10.211.55.3's password: 
Last login: Sun Apr 26 14:41:31 2020
[kenny@centos-linux ~]$ ls
android  Desktop  Documents  Downloads  fontconfig  gitlab-ce-11.9.9-ce.0.el7.x86_64.rpm  hhhha.txt  Music  Pictures  Public  Templates  Videos
[kenny@centos-linux ~]$ uname -r
3.10.0-1062.el7.x86_64
[kenny@centos-linux ~]$ sudo yum update
[sudo] kenny 的密码:
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: mirror.shastacoe.net
 * extras: mirror.teklinks.com
 * updates: ftp.usf.edu
.
.
.

完毕!
[kenny@centos-linux ~]$ sudo yum remove docker  docker-common docker-selinux docker-engin
已加载插件:fastestmirror, langpacks
参数 docker 没有匹配
参数 docker-common 没有匹配
参数 docker-selinux 没有匹配
参数 docker-engin 没有匹配
不删除任何软件包
[kenny@centos-linux ~]$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: mirror.fileplanet.com
 * extras: mirrors.xmission.com
 * updates: centos.mirror.constant.com
软件包 yum-utils-1.1.31-52.el7.noarch 已安装并且是最新版本
软件包 device-mapper-persistent-data-0.8.5-1.el7.x86_64 已安装并且是最新版本
软件包 7:lvm2-2.02.185-2.el7_7.2.x86_64 已安装并且是最新版本
无须任何处理
[kenny@centos-linux ~]$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
已加载插件:fastestmirror, langpacks
adding repo from: https://download.docker.com/linux/centos/docker-ce.repo
grabbing file https://download.docker.com/linux/centos/docker-ce.repo to /etc/yum.repos.d/docker-ce.repo
repo saved to /etc/yum.repos.d/docker-ce.repo
[kenny@centos-linux ~]$ yum list docker-ce --showduplicates | sort -r
已加载插件:fastestmirror, langpacks
可安装的软件包
 * updates: mirrors.ocf.berkeley.edu
 * extras: mirrors.xmission.com
docker-ce.x86_64            3:19.03.8-3.el7                     docker-ce-stable
docker-ce.x86_64            3:19.03.7-3.el7                     docker-ce-stable
docker-ce.x86_64            3:19.03.6-3.el7                     docker-ce-stable
.
.
.
docker-ce.x86_64            17.03.0.ce-1.el7.centos             docker-ce-stable
Determining fastest mirrors
 * base: mirror.keystealth.org
[kenny@centos-linux ~]$ sudo yum install docker-ce-18.03.1.ce
已加载插件:fastestmirror, langpacks
.
.
.                                                                                                                                                                                                                                                                                                                            

作为依赖被安装:
  container-selinux.noarch 2:2.107-3.el7                                                                                                                                                  pigz.x86_64 0:2.3.3-1.el7.centos                                                                                                                                                 

完毕!
[kenny@centos-linux ~]$ sudo systemctl start docker
[kenny@centos-linux ~]$ sudo systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
[kenny@centos-linux ~]$ curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  6904  100  6904    0     0   6197      0  0:00:01  0:00:01 --:--:--  6203
Detected operating system as centos/7.
Checking for curl...
Detected curl...
Downloading repository file: https://packages.gitlab.com/install/repositories/runner/gitlab-runner/config_file.repo?os=centos&dist=7&source=script
done.
Installing pygpgme to verify GPG signatures...
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: repos.lax.quadranet.com
 * extras: mirrors.xmission.com
 * updates: bay.uchicago.edu
runner_gitlab-runner-source/signature                                                                                                                                                                                                                                                                                                                |  862 B  00:00:00     
从 https://packages.gitlab.com/runner/gitlab-runner/gpgkey 检索密钥
导入 GPG key 0x51312F3F:
 用户ID     : "GitLab B.V. (package repository signing key) <packages@gitlab.com>"
 指纹       : f640 3f65 44a3 8863 daa0 b6e0 3f01 618a 5131 2f3f
 来自       : https://packages.gitlab.com/runner/gitlab-runner/gpgkey
从 https://packages.gitlab.com/runner/gitlab-runner/gpgkey/runner-gitlab-runner-366915F31B487241.pub.gpg 检索密钥
runner_gitlab-runner-source/signature                                                                                                                                                                                                                                                                                                                |  951 B  00:00:00 !!! 
runner_gitlab-runner-source/primary                                                                                                                                                                                                                                                                                                                  |  175 B  00:00:02     
软件包 pygpgme-0.3-9.el7.x86_64 已安装并且是最新版本
无须任何处理
Installing yum-utils...
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: mirror.centos.lax1.serverforge.org
 * extras: mirrors.xmission.com
 * updates: mirrors.ocf.berkeley.edu
软件包 yum-utils-1.1.31-52.el7.noarch 已安装并且是最新版本
无须任何处理
Generating yum cache for runner_gitlab-runner...
导入 GPG key 0x51312F3F:
 用户ID     : "GitLab B.V. (package repository signing key) <packages@gitlab.com>"
 指纹       : f640 3f65 44a3 8863 daa0 b6e0 3f01 618a 5131 2f3f
 来自       : https://packages.gitlab.com/runner/gitlab-runner/gpgkey
Generating yum cache for runner_gitlab-runner-source...

The repository is setup! You can now install packages.
[kenny@centos-linux ~]$ sudo yum install gitlab-runner
[sudo] kenny 的密码:
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: repos.lax.quadranet.com
 * extras: mirrors.xmission.com
 * updates: centos.mirror.constant.com
runner_gitlab-runner/x86_64/signature                                                                                                                                                                                                                                                                                                                |  862 B  00:00:00     
runner_gitlab-runner/x86_64/signature                                                                                                                                                                                                                                                                                                                | 1.0 kB  00:00:00 !!! 
runner_gitlab-runner-source/signature                                                                                                                                                                                                                                                                                                                |  862 B  00:00:00     
runner_gitlab-runner-source/signature                                                                                                                                                                                                                                                                                                                |  951 B  00:00:00 !!! 


完毕!
[kenny@centos-linux ~]$ sudo gitlab-runner register
[sudo] kenny 的密码:
Runtime platform                                    arch=amd64 os=linux pid=29719 revision=ce065b93 version=12.10.1
Running in system-mode.                            
                                                   
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
http://10.211.55.3:8888/
Please enter the gitlab-ci token for this runner:
qusy3u_yhLNZzMBHgz9p
Please enter the gitlab-ci description for this runner:
[centos-linux.shared]: android test runner
Please enter the gitlab-ci tags for this runner (comma separated):
android
Registering runner... succeeded                     runner=qusy3u_y
Please enter the executor: custom, docker-ssh, virtualbox, docker+machine, docker-ssh+machine, docker, parallels, shell, ssh, kubernetes:
docker
Please enter the default Docker image (e.g. ruby:2.6):
jangrewe/gitlab-ci-android
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 
[kenny@centos-linux ~]$ cd /etc/gitlab-runner/
-bash: cd: /etc/gitlab-runner/: 权限不够
[kenny@centos-linux ~]$ su
密码:
[root@centos-linux kenny]# cd /etc/gitlab-runner/
[root@centos-linux gitlab-runner]# ls
config.toml
[root@centos-linux gitlab-runner]# vi config.toml 
[root@centos-linux gitlab-runner]# cd -
/home/kenny
[root@centos-linux kenny]# pwd
/home/kenny
[root@centos-linux kenny]# vi /etc/gitlab-runner/config.toml 
[root@centos-linux kenny]# ls
android  Desktop  Documents  Downloads  fontconfig  gitlab-ce-11.9.9-ce.0.el7.x86_64.rpm  hhhha.txt  Music  Pictures  Public  Templates  Videos
[root@centos-linux kenny]# pwd
/home/kenny
[root@centos-linux kenny]# exit
exit
[kenny@centos-linux ~]$ pwd
/home/kenny
[kenny@centos-linux ~]$ ls
android  Desktop  Documents  Downloads  fontconfig  gitlab-ce-11.9.9-ce.0.el7.x86_64.rpm  hhhha.txt  Music  Pictures  Public  Templates  Videos
[kenny@centos-linux ~]$ mkdir android-cache
[kenny@centos-linux ~]$ cd android-cache/
[kenny@centos-linux android-cache]$ ls
[kenny@centos-linux android-cache]$ cd ..
[kenny@centos-linux ~]$ cd android
[kenny@centos-linux android]$ ls
[kenny@centos-linux android]$ cd ..
[kenny@centos-linux ~]$ cd android-cache/
[kenny@centos-linux android-cache]$ mkdir keystore
[kenny@centos-linux android-cache]$ ls
keystore

https://blog.csdn.net/Captive_Rainbow_/article/details/90407356

Android通过add添加多个Fragment事件透传问题

在通过add添加多个Fragment的过程中,如果新fragment某空白区域对应上一fragment的某个控件,点击该空白区域会响应上一fragment控件点击事件,也就是事件透传过去了,解决该问题最简单的方法即为在fragment的根布局文件中加入android:clickable = true属性即可,或者对根布局设个空的点击事件也行。