标签: Android

有什么方法能够检测手机上已安装的 APP 中有哪些支持 fcm 的?

有什么方法能够检测手机上已安装的 APP 中有哪些支持 fcm 的?

 

*近一个月从 ios 换到 MIUI ( eu 版),这才发现原来 Android 党天天说推送比不过 iOS 并不是夸大其词。iOS 上通知虽然泛滥吧,但我可以主动忽略它,顶多碍眼一些罢了,可在国内用 Android 你就要担心通知是不是漏了? fcm 有没有 disconnected ?新安装的 APP 支不支持 fcm 亦或是 mipush ?需不需要打开自启动开关?所以装国内 APP 时也尽可能选择 play 版,可奈何 play 版也不一定就能走 fcm… 于是就问问大家有没有什么方便的办法能够知道一款 APP 是否接入 fcm 推送?

15 条回复    2021-07-11 10:50:07 +08:00

lzsqq754318010
    1

lzsqq754318010   22 天前

可能有人会说看 fcm 的 log,但问题是有些 App 你不知道它什么时候会有推送,而且频率也可能不是很高。
learningman
    2

learningman   22 天前 via Android

黑阈看得到
pakro888
    3

pakro888   22 天前

绿色守护也能看到
lzsqq754318010
    4

lzsqq754318010   22 天前

感谢上面二位,我去瞧瞧。
john6lq
    5

john6lq   22 天前 via iPhone

对不起,请问 FCM 是?
voiyy
    6

voiyy   22 天前   ❤️ 2

libchecker app
JensenQian
    7

JensenQian   22 天前 via Android

@john6lq https://firebase.google.com/docs/cloud-messaging
YvanGu
    8

YvanGu   22 天前

斑朵,可以看到支持的各种推送
dingwen07
    9

dingwen07   22 天前 via iPhone

在国内 FCM 确实不如 APN
国产 App 兼容 FCM 的很少,你就当没有吧,印象中通知有点用的也就个闲鱼和微信国际版是走 FCM 的。。。
国外 App 还是很好用的,基本上有通知需求都会走 FCM 。
dingwen07
    10

dingwen07   22 天前 via iPhone

我是用绿色守护看,国产 app 全部不给自启动,国外 app 操作系统默认允许自启动

 

terabithia
    11

terabithia   22 天前

@dingwen07 走 FCM 也还是要允许自启吧,只是禁止后台。我现在用的 play 上下载的微信和钉钉可以走 FCM,其他的好像也没啥有推送的需求……
dingwen07
    12

dingwen07   22 天前 via iPhone

@terabithia #11 允许自启动是必须的,禁止后台我就不清楚了,反正我用的三星上面允许自己和禁止后台是一起的

微信必须得是外国手机号注册的,不然不走 FCM,我主帐号是国内的就很难受


terabithia
    13

terabithia   22 天前

@dingwen07 我刚刚发现我把 twitter 禁止自启好像也有推送。我的微信是国内手机号码注册的,我把微信也设置为走代理,而且是全局,可以走 FCM 的,不过这个要看代理的网络质量,好在我对推送也不是那么敏感,随缘吧
janus77
    14

janus77   21 天前

这个你做不到完全准确的
我就说一句,推送是主动发起的,就算他在代码层面支持了推送,某些消息运营就是不想在 fcm 这个渠道里面发,你能怎么办?
0A0
    15

0A0   19 天前 via Android

eu 版的 fcm 是完整支持的。
剩下就是各个 app 自己的锅了。cn 版需要打开自启动,或者同时打开后台。eu 版正常不需要,只是有的 app 不按标准来,才需要你检查自启动,后台等额外的操作。
比如 line,不管 cn 和 eu,开启自启动后都能正常推送 fcm 信息,属于正常。只是不正常的 app 太多了而已。

Android新手,内存泄漏问题

问题描述

  • A = 主页面 fragment
  • B = 输入内容页 fragment
  • C = 检索结果页 fragment 我在 A 中进行 replace 跳转到一个 B,完成输入后 replace 跳转到 C,此时使用 home 键进入后台运行,发生内存泄漏问题

报错情况

D/HomePageFragment: BaseFragment-->onPause()
D/DiscoveryFragment: BaseFragment-->onPause()
D/TvItemsFragment: BaseFragment-->onPause()
D/MainActivity: BaseActivity-->onPause()
D/LeakCanary: Scheduling check for retained objects in 5000ms because app became invisible
D/HomePageFragment: BaseFragment-->onStop()
D/DiscoveryFragment: BaseFragment-->onStop()
D/CommendFragment: BaseFragment-->onStop()
D/TvItemsFragment: BaseFragment-->onStop()
D/MainActivity: BaseActivity-->onStop()
D/MainActivity: BaseActivity-->onSaveInstanceState()

D/LeakCanary: ====================================
    HEAP ANALYSIS RESULT
    ====================================
    1 APPLICATION LEAKS
    
    References underlined with "~~~" are likely causes.
    Learn more at https://squ.re/leaks.
    
    53078 bytes retained by leaking objects
    Signature: b01ba777d9ce636d68e71237f54fafe95ee827f8
    ┬───
    │ GC Root: System class
    │
    ├─ android.view.inputmethod.InputMethodManager class
    │    Leaking: NO (InputMethodManager↓ is not leaking and a class is never leaking)
    │    ↓ static InputMethodManager.sInstance
    ├─ android.view.inputmethod.InputMethodManager instance
    │    Leaking: NO (ViewRootImpl↓ is not leaking and InputMethodManager is a singleton)
    │    ↓ InputMethodManager.mCurRootView
    ├─ android.view.ViewRootImpl instance
    │    Leaking: NO (ViewPager2$RecyclerViewImpl↓ is not leaking and ViewRootImpl#mView is not null)
    │    ↓ ViewRootImpl.mImeFocusController
    ├─ android.view.ImeFocusController instance
    │    Leaking: NO (ViewPager2$RecyclerViewImpl↓ is not leaking)
    │    ↓ ImeFocusController.mNextServedView
    ├─ androidx.viewpager2.widget.ViewPager2$RecyclerViewImpl instance
    │    Leaking: NO (SearchFragment↓ is not leaking and View attached)
    │    mContext instance of com.moviemore.android.ui.MainActivity with mDestroyed = false
    │    View.parent androidx.viewpager2.widget.ViewPager2 attached as well
    │    View#mParent is set
    │    View#mAttachInfo is not null (view attached)
    │    View.mID = R.id.null
    │    View.mWindowAttachCount = 1
    │    ↓ ViewPager2$RecyclerViewImpl.mAdapter
    ├─ com.moviemore.android.ui.common.ui.BaseViewPagerFragment$VpAdapter instance
    │    Leaking: NO (SearchFragment↓ is not leaking)
    │    ↓ BaseViewPagerFragment$VpAdapter.mFragmentManager
    ├─ androidx.fragment.app.FragmentManagerImpl instance
    │    Leaking: NO (SearchFragment↓ is not leaking)
    │    ↓ FragmentManagerImpl.mFragmentStore
    ├─ androidx.fragment.app.FragmentStore instance
    │    Leaking: NO (SearchFragment↓ is not leaking)
    │    ↓ FragmentStore.mActive
    ├─ java.util.HashMap instance
    │    Leaking: NO (SearchFragment↓ is not leaking)
    │    ↓ HashMap.table
    ├─ java.util.HashMap$Node[] array
    │    Leaking: NO (SearchFragment↓ is not leaking)
    │    ↓ HashMap$Node[].[6]
    ├─ java.util.HashMap$Node instance
    │    Leaking: NO (SearchFragment↓ is not leaking)
    │    ↓ HashMap$Node.value
    ├─ androidx.fragment.app.FragmentStateManager instance
    │    Leaking: NO (SearchFragment↓ is not leaking)
    │    ↓ FragmentStateManager.mFragment
    ├─ com.moviemore.android.ui.search.SearchFragment instance
    │    Leaking: NO (Fragment#mFragmentManager is not null)
    │    ↓ SearchFragment.rootView
    │                     ~~~~~~~~
    ╰→ android.widget.LinearLayout instance
    ​     Leaking: YES (ObjectWatcher was watching this because com.moviemore.android.ui.search.SearchFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks))
    ​     key = 888671cb-ecda-4776-b4dd-b4f2350acb32
    ​     watchDurationMillis = 7210
    ​     retainedDurationMillis = 2208
    ​     mContext instance of com.moviemore.android.ui.MainActivity with mDestroyed = false
    ​     View#mParent is null
    ​     View#mAttachInfo is null (view detached)
    ​     View.mWindowAttachCount = 1
    ====================================
    0 LIBRARY LEAKS

3 条回复    2021-07-29 16:08:15 +08:00

JellyBeanX
    1

JellyBeanX   2 天前

要么,在 A 的 onDestroyView() 中销毁 LinearLayout 的实例,要么把 replace 换成 show & hide
JellyBeanX
    2

JellyBeanX   2 天前

@JellyBeanX 说错了,是销毁持有 linearLayout 实例的对象
ukyoo
    3

ukyoo   59 分钟前

onDestroyView()后要手动把成员变量的 View 置空, 因为下一次 onCreateView()还会重新 infalte 一次布局, 这个成员变量就没什么用了, 视为泄露

Android:MVC模式(下)

在上一篇文章中,我们将 View 类单独出来并完成了设计和编写。这次我们将完成 Model 类,并通过 Controller 将两者连接起来,完成这个计算器程序。

模型(Model)就是程序中封装了数据,并定义了操作和处理这些数据的逻辑的对象。在计算器的例子中,就是处理输入的操作数和运算符,并计算返回结果。Let’s Go
(注意:示例中直接使用 double 类型来处理数据,但严格来说很多语言的浮点数计算都是不精确的)

一,设计模型的接口

在程序构建之初,我们首先考虑的应该是各模块间的封装和扩展,设计好模块的接口,然后再实现模块的细节,*后把模块组合起来构成整个程序。这就是面向接口编程的概念。

思考一下 Model 类,它主要实现三个功能,1,接受操作数输入;2,接受运算符输入并返回计算结果;3,重置。这些功能主要都是被外部使用的,所以基于此来设计接口,对其它类而言,只需知道该接口定义了什么方法然后使用就行,而不需要了解实现细节。

创建 com.test.interfaces 包,并右键选择 New -> Interface 创建一个名为 ICalculator 的接口(一般接口命名都以大写字母 I 开头)

android_5_interface1

创建 com.test.model 包,并创建 CalModel 类,同时实现 ICalculator 接口。

android_5_calmodel

二,通过栈和递归函数实现计算器算法

计算器的计算规则很简单,从左到右计算,不考虑运算符的优先级,例如:2 + 1 * 2 – 3,相当于: (((2 + 1) * 2) – 3) = 3;这样保证编写的简易性。

我们知道,栈是一种后进先出的数据结构,我们通过一个栈 dataStack 记录输入的运算数和操作符。如图所示:android_5_datastack

所谓的递归函数,就是接受有限的自然数,通过重复调用自身,*终得到一个自然数结果的函数。算法的核心就是设计这样一个递归函数 popOpOffStack 来对 dataStack 进行求解,过程如图:

android_5_popstack

三,编写程序

按上面的思路,popOpOffStack 可以单独写出,并不需要实例化后才能使用,所以将 popOpOffStack 设为 CalModel 的静态方法则可,如图:

android_5_javapop

完成编写后,可以构建一个栈来测试一下,因为返回的是 double 类型,所以结果是 3.0,如图:

android_5_test1

android_5_test2

核心算法完成后,完成对运算数和操作符的输入的处理并补充其它细节。这样我们的 CalModel 就完成了。

android_5_javamodel

四,通过Controller将Model和View连接

在 Android 的文档中,Activity 是被推荐作为 Controller 的角色,负责视图和模型的协调、沟通。而从 Activity 所拥有的功能的角度看,也是*合适的选择。至此,代码结构也清晰了。MainActivity 将作为 Controller 协调视图和模型,并对一些数据及细节进行处理。其代码如下:

android_5_controller

通过 MainActivity 将模型和视图连接起来后,我们的计算器也*终完成了。可以运行测试一下。

五,结束及回答

本次要点:
1)通过 MVC 设计,使计算器程序有了较好的扩展和维护能力,假如使用另一种计算器算法,只需创建另一实现 ICalculator 接口的 Model 类,并代替原来的 CalMode 则可。当需要增加功能时,可以将代码增加到对应的模块处而不用随意写在一起。

2)这次只介绍了*基本的 MVC 知识,而至于 MVC 模式更高级的使用以及模块间的通信会在后面再讲。从代码的编写中,大家可能会发现 MainActivity 其实很难进行抽象,因为都是一些连接的逻辑和细碎代码。这也就是为什么 MVC 概念提出这么久以来,Model 和 View 的封装技术有了长足进步,而 Controller 却没什么突破的原因之一。作为前端,这方面大家可以对比一下现时流行起来的 MVC 库,对 Controller 角色的处理。而苹果在 Cocoa 中对 Controller 的封装也是很有特色,有机会会介绍对比一下。

3)例子中的细节处理还可以处理得更好,就留给大家修改。另外,有时间的话可以尝试使用经典的双栈算法来实现这个计算器,替换 CalModel 类。

在上几篇文章中大家提到的問题在这里总结回答一下:
1)什么时候用回调和 MVC 的设计为什么可以提高维护和扩展性:对于前者,当我们设计一个模块时,模块除提供方法外,自身发生的事是需要被外界了解时,或外界对这个模块发生的事是“渴望”得知的,这个时候就需要进行回调处理了。对于后者,在这次的文章中讲得比也较清晰了。

2)创建工程时,为什么会多出一些 support-v4、 support-v7 等这些包,这是 Android 的一些高级 API 或组件为在低版本中向下兼容实现而自动加载到工程中的,对自身程序并没有什么影响。

Android:MVC模式(上)

很多Android的入门书籍,在前面介绍完布局后就会逐个介绍组件,然后开始编写组件使用的例子。每每到此时小伙伴们都可能会有些疑问:是否应该先啃完一本《Java编程思想》学点 Java 知识呢?这些组件会使用了,但如何更好组织起来呢?

其实,Android 和 iOS 已经把应用层级别的东西封装得比较简单易用,也配有丰富的文档与之对应,所以倒不必担心如何使用。而实际上,我想让大家通过这个系列的文章更关注和学习下面两点,我也会在例子的选取上多涉及这些方面的知识。

  • 编程的思想。正如学会英语,并不一定就能写出好的英文文章。
  • 查找学习的能力。知道如何发现问题的关键点,然后去找方法解决。

MVC 是软件工程中*基本的设计模式,也是组织良好代码的基础,Android 和 iOS 中也一样,所以在接下来的三篇文章中,将会介绍如何通过 MVC 模式制作一个简易计算器应用,Let’s Go!

这个高大上(偷笑)的计算器界面如下,这次先完成界面部分。

android_3_calculator

一,界面还原准备

首先,打开 Eclipse,创建一个 Android 工程,并命名为:Calculator(如下图)

android_3_new

此时,会默认打开 MainActivity.java 和 activity_main.xml 两个文件,activity_main.xml 为界面布局文件,MainActivity.java 为程序入口文件(这次先不用编写)。
同时,我们将 res > values > styles.xml 文件打开,activity_main.xml 和 styles.xml 之间的关系就相当于 html 和 css。

android_3_files

我们知道,Android 中有 LinearLayout,RelativeLayout 等布局元素,这次我们就先用 LinearLayout 来完成界面的布局。

🙂 首先等我请出本系列课程的助教:Google 大神

LinearLayout(线性布局),大神给出的定义是:
将子视图元素按水平或垂直方向一个接一个排列的视图组合(布局)。

从上一篇文章中我们知道 View 类由两类属性控制其视觉呈现,所以 LinearLayout 有其自有属性,而处于其内的子元素则可以使用 LinearLayout.LayoutParams 定义的属性,那怎样去找这些属性呢?当然是去问我们的大神了。

LinearLayout 类参考

从 Summary 的 XML Attributes 中可以知道这些属性的信息概要,点击每个属性,下面都有详细的介绍。

android_3_attribute1

几个常用属性:
1,android:orientation  通过设置值为 “horizontal” 或 “vertical” 让子元素按水平或垂直排列。
2,android:gravity  设置其内容(文字、视图)在该元素内的位置,通过 “|” 号分隔多个值(top,bottom,left,right,center,center_vertical,center_horizontal)。
3,android:baselineAligned  设置为”false”则统一对齐的基线,主要用于设置了不同 gravity 的可显示文字的 View 元素。这里先不展开。

android_3_example1

那 LinearLayout.LayoutParams 有什么属性呢,同样我们从大神那找到:

LinearLayout.LayoutParams 类参考

android_3_attribute2

从上篇文章可知 xxx.LayoutParams 定义的属性是用于布局内的元素上的。

1,android:layout_gravity  让子元素设置其相对于父元素中的位置,其设值和 android:gravity 一样。可能有人就会疑问了,那这两个属性有什么区别呢?
简单点理解,android:gravity 是应用于自身所包含的内容(这个内容可以是文字或子视图),而 android:layout_gravity 则是应用在自身。

(这里还要指出一个大家在线性布局中可能会遇到的问题:android:layout_gravity 设值失效问题。例如在设定了 android:orientation = “vertical” 的 LinearLayout 中,设定一个 TextView 的 layout_grivity = “top” 或者 layout_grivity = “bottom” 是失效的。同样,在 android:orientation = “horizontal” 中设定元素的 “left” 或 “right” 也会一样。为什么会这样呢?就留给小伙伴们思考了,其实想想这样设定还算是合理的。)

2,android:layout_weight  大神也有偷懒的时候,这里竟然没有说明。大神把它放在另一个地方介绍了。性质类似 Css 的弹性盒,对 -webkit-flex 设值,相当于显示权重。情况会很多,篇幅问题只能留在以后的文章加以说明。这次界面制作使用 layout_weight 的策略是:每个元素都占用“足够大的空间”,然后各自权重为1,这样一来就平均了。

android_3_example2

请小伙伴们再看一次线性布局的介绍,   LinearLayout 线性布局

在准备部分,我没有直接列出所有属性来介绍。而是更想展示如何去思考和查找解决办法的过程。对于文章没展开部分,可以去查一查,培养阅读文档的习惯 🙂

二,界面的制作

前面废话多了, 既然用线性布局,界面就直接用一个 TextView + 4个LinearLayout 垂直排列做布局。如下图:

android_3_layout1

正如把 css 写在 html 中是“下流”的写法,那么我们应该“上流”点,把样式分离写在 styles.xml 中,activity_main.xml 中则通过@style/{ClassName} 的方式留下我们锚点则可。
这里,Eclipse 会提示xml 中存在若干错误,因为我们还没在 styles.xml 中建立相应的资源名,不用理会。也会提示了一个修改建议,说把字符硬编码进TextView了,也可不理。

此时转到 styles.xml 中,建立起相应的“类名”,(注意:这里先把 AppTheme 设置为:android:Theme.NoTitleBar )

android_3_style1

建立完“类名”后,我们就可以编写<body>的样式了,这里设置为垂直排列。
我们还将建立一个资源文件,用于设定颜色值,就如同 strings.xml 的作用一样。

android_3_color1

android_3_color2

android_3_color3

这里先把我们会用到的颜色值都设定了,包括按钮和文字的颜色。

android_3_color4

继续编写我们的 styles.xml 文件,通过 layout_weight 的设定,我们将 TextView 和 4个 LinearLayout 平分屏幕的空间。并且为我们的 TextView 添加相应的样式,而准备用于放置按钮的 LinearLayout 则设定为水平排列:

android_3_style2

此时转到 activity_main.xml 并在 xml 编辑框底部点选 Graphical Layout 这个tab,可以预览还原了的界面。和我们预想一样,TextView 占了空间的1/5。

android_3_ui1

好了,可以开始编写我们的按钮了,我们将用 Button 元素实现按钮,用 btn_operand 命名操作数的样式,用 btn_operate 命名操作符的样式,并且 btn_operate 将会继承 btn_operand 的样式,然后重新设置背景色和文字色,这些就和使用 Css 一样。

android_3_ui2

android_3_ui3

预览一下界面。和我们设想的一样。

android_3_ui4

剩下的就交由小伙伴继续完成啦 🙂  另外,鉴于个人能力有限,在文章中未免会出现错误,欢迎大家的指正,感谢大家!

后话:

下一篇文章开始将会涉及 Java 代码,有条件的同学请备一本《Java编程思想》,机械工业出版社的,网上应该有很多 pdf ,当然不会让你啃了它,太浪费时间。因为在编写代码的过程中,或多或少会涉及 Java 的东西,我会指出需要看的那部分,从而把基础的 Java 知识学了。另外,应小伙伴们的要求,我会找地方提供编写的代码下载。题外话,《Java编程思想》的确是本好书,作为参考或者学习也好,可以考虑备一本。

Android中Handler引起的内存泄露

在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用。通常我们的代码会这样实现。

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ... 
    }
  }
}

但是,其实上面的代码可能导致内存泄露,当你使用Android lint工具的话,会得到这样的警告

In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class

看到这里,可能还是有一些搞不清楚,代码中哪里可能导致内存泄露,又是如何导致内存泄露的呢?那我们就慢慢分析一下。

1.当一个Android应用启动的时候,会自动创建一个供应用主线程使用的Looper实例。Looper的主要工作就是一个一个处理消息队列中的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。主线程中的Looper生命周期和当前应用一样长。

2.当一个Handler在主线程进行了初始化之后,我们发送一个target为这个Handler的消息到Looper处理的消息队列时,实际上已经发送的消息已经包含了一个Handler实例的引用,只有这样Looper在处理到这条消息时才可以调用Handler#handleMessage(Message)完成消息的正确处理。

3.在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用。静态的内部类不会持有外部类的引用。关于这一内容可以查看细话Java:”失效”的private修饰符

确实上面的代码示例有点难以察觉内存泄露,那么下面的例子就非常明显了

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

分析一下上面的代码,当我们执行了Activity的finish方法,被延迟的消息会在被处理之前存在于主线程消息队列中10分钟,而这个消息中又包含了Handler的引用,而Handler是一个匿名内部类的实例,其持有外面的SampleActivity的引用,所以这导致了SampleActivity无法回收,进行导致SampleActivity持有的很多资源都无法回收,这就是我们常说的内存泄露。

注意上面的new Runnable这里也是匿名内部类实现的,同样也会持有SampleActivity的引用,也会阻止SampleActivity被回收。

要解决这种问题,思路就是避免使用非静态内部类,继承Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理。另外关于同样也需要将Runnable设置为静态的成员属性。注意:一个静态的匿名内部类实例不会持有外部类的引用。 修改后不会导致内存泄露的代码如下

public class SampleActivity extends Activity {

  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;

    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }

  private final MyHandler mHandler = new MyHandler(this);

  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

其实在Android中很多的内存泄露都是由于在Activity中使用了非静态内部类导致的,就像本文提到的一样,所以当我们使用时要非静态内部类时要格外注意,如果其实例的持有对象的生命周期大于其外部类对象,那么就有可能导致内存泄露。个人倾向于使用文章的静态类和弱引用的方法解决这种问题。

Activity启动流程–Activity启动的概要流程

概述

Android中启动某个Activity,将先启动Activity所在的应用。应用启动时会启动一个以应用包名为进程名的进程,该进程有一个主线程,叫ActivityThread,也叫做UI线程。

本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究。

  • 深入理解Activity启动流程(二)–Activity启动相关类的类图
  • 深入理解Activity启动流程(三)–Activity启动的详细流程1
  • 深入理解Activity启动流程(三)–Activity启动的详细流程2
  • 深入理解Activity启动流程(四)–Activity Task的调度算法

Activity启动时的概要交互流程

用户从Launcher程序点击应用图标可启动应用的入口Activity,Activity启动时需要多个进程之间的交互,Android系统中有一个zygote进程专用于孵化Android框架层和应用层程序的进程。还有一个system_server进程,该进程里运行了很多binder service,例如ActivityManagerService,PackageManagerService,WindowManagerService,这些binder service分别运行在不同的线程中,其中ActivityManagerService负责管理Activity栈,应用进程,task。

Activity启动时的概要交互流程如下图如下所示(点击图片可看大图):

activity_start_flow

用户在Launcher程序里点击应用图标时,会通知ActivityManagerService启动应用的入口Activity,ActivityManagerService发现这个应用还未启动,则会通知Zygote进程孵化出应用进程,然后在这个dalvik应用进程里执行ActivityThread的main方法。应用进程接下来通知ActivityManagerService应用进程已启动,ActivityManagerService保存应用进程的一个代理对象,这样ActivityManagerService可以通过这个代理对象控制应用进程,然后ActivityManagerService通知应用进程创建入口Activity的实例,并执行它的生命周期方法。

后续博客将介绍Activity的详细启动流程。

RxJava 简介

前言

提升开发效率,降低维护成本一直是开发团队永恒不变的宗旨。近一年来国内的技术圈子中越来越多的开始提及Rx,经过一段时间的学习和探索之后我也深深的感受到了RxJava的魅力。它能帮助我们简化代码逻辑,提升代码可读性。这对于开发效率的提升、后期维护成本的降低帮助都是巨大的。个人预测RxJava一定是2016年的一个大趋势,所以也有打算将它引入到公司现有的项目中来,写这一系列的文章主要也是为了团队内部做技术分享。

由于我本人是个Android程序猿,因此这一系列文章中的场景都是基于Android平台的。如果你是个Java Web工程师或者是其它方向的那也没关系,我会尽量用通俗的语言将问题描述清楚。

响应式编程

在介绍RxJava前,我们先聊聊响应式编程。那么什么是响应式编程呢?响应式编程是一种基于异步数据流概念的编程模式。数据流就像一条河:它可以被观测,被过滤,被操作,或者为新的消费者与另外一条流合并为一条新的流。

响应式编程的一个关键概念是事件。事件可以被等待,可以触发过程,也可以触发其它事件。事件是唯一的以合适的方式将我们的现实世界映射到我们的软件中:如果屋里太热了我们就打开一扇窗户。同样的,当我们的天气app从服务端获取到新的天气数据后,我们需要更新app上展示天气信息的UI;汽车上的车道偏移系统探测到车辆偏移了正常路线就会提醒驾驶者纠正,就是是响应事件。

今天,响应式编程*通用的一个场景是UI:我们的移动App必须做出对网络调用、用户触摸输入和系统弹框的响应。在这个世界上,软件之所以是事件驱动并响应的是因为现实生活也是如此。

本章节中部分概念摘自《RxJava Essentials》一书

RxJava的来历

Rx是微软.Net的一个响应式扩展,Rx借助可观测的序列提供一种简单的方式来创建异步的,基于事件驱动的程序。2012年Netflix为了应对不断增长的业务需求开始将.NET Rx迁移到JVM上面。并于13年二月份正式向外展示了RxJava。
从语义的角度来看,RxJava就是.NET Rx。从语法的角度来看,Netflix考虑到了对应每个Rx方法,保留了Java代码规范和基本的模式。

%title插图%num
RxJava来历

什么是RxJava

那么到底什么是RxJava呢?我对它的定义是:RxJava本质上是一个异步操作库,是一个能让你用*其简洁的逻辑去处理繁琐复杂任务的异步事件库。

RxJava好在哪

Android平台上为已经开发者提供了AsyncTask,Handler等用来做异步操作的类库,那我们为什么还要选择RxJava呢?答案是简洁!RxJava可以用非常简洁的代码逻辑来解决复杂问题;而且即使业务逻辑的越来越复杂,它依然能够保持简洁!再配合上Lambda用简单的几行代码分分钟就解决你负责的业务问题。简直逼格爆表,拿它装逼那是*好的!

多说无益,上代码!

假设我们安居客用户App上有个需求,需要从服务端拉取上海浦东新区塘桥板块的所有小区Community[] communities,每个小区下包含多套房源List<House> houses;我们需要把塘桥板块的所有总价大于500W的房源都展示在App的房源列表页。用于从服务端拉取communities需要发起网络请求,比较耗时,因此需要在后台运行。而这些房源信息需要展示到App的页面上,因此需要在UI线程上执行。(此例子思路来源于扔物线的给Android开发者的RxJava详解一文)

new Thread() {
        @Override
        public void run() {
            super.run();
            //从服务端获取小区列表
            List<Community> communities = getCommunitiesFromServer();
            for (Community community : communities) {
                List<House> houses = community.houses;
                for (House house : houses) {
                    if (house.price >= 5000000) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                //将房子的信息添加到屏幕上
                                addHouseInformationToScreen(house);
                            }
                        });
                    }
                }
            }
        }
    }.start();

使用RxJava的写法是这样的:

Observable.from(getCommunitiesFromServer())
            .flatMap(new Func1<Community, Observable<House>>() {
                @Override
                public Observable<House> call(Community community) {
                    return Observable.from(community.houses);
                }
            }).filter(new Func1<House, Boolean>() {
                @Override
                public Boolean call(House house) {
                    return house.price>=5000000;
                }
            }).subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Action1<House>() {
                @Override
                public void call(House house) {
                    //将房子的信息添加到屏幕上
                    addHouseInformationToScreen(house);
                }
            });

从上面这段代码我们可以看到:虽然代码量看起来变复杂了,但是RxJava的实现是一条链式调用,没有任何的嵌套;整个实现逻辑看起来异常简洁清晰,这对我们的编程实现和后期维护是有巨大帮助的。特别是对于那些回调嵌套的场景。配合Lambda表达式还可以简化成这样:

  Observable.from(getCommunitiesFromServer())
            .flatMap(community -> Observable.from(community.houses))
            .filter(house -> house.price>=5000000).subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(this::addHouseInformationToScreen);

简洁!有美感!这才是一个有情怀的程序员应该写出来的代码。

看完这篇文章大家应该能够理解RxJava为什么会越来越火了。它能*大的提高我们的开发效率和代码的可读性!当然了RxJava的学习曲线也是比较陡的,在后面的文章我会对主要的知识点做详细的介绍,敬请关注!

Android 监控软键盘确定 搜索 按钮并赋予点击事件

android的实践开发中,为了界面的美观,往往那些搜索框并没有带搜索按钮,而是调用了软键盘的搜索按钮,完成这次时间
  • 1
  • 2

这里写图片描述
这里写图片描述

好吧!直接上代码!

<EditText
        android:id="@+id/my_chat_seach"
        android:layout_width="fill_parent"
        android:layout_height="23dp"
        android:layout_centerVertical="true"
        android:layout_marginRight="6dip"
        android:layout_toRightOf="@id/my_seach_item_1_button"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:hint="@string/search"
        android:imeOptions="actionSearch"
        android:singleLine="true"
        android:textColor="#8e8787"
        android:textSize="13sp" />

xml配置文件中 *重要的一个属性是: android:imeOptions=”actionSearch”,从而调用软键盘时,回车键就会显示搜索二字。
同时在androidMainfest.xml文件中在此Activity中写入 android:windowSoftInputMode=”adjustPan”,可以防止软键盘会把原来的界面挤上去的问题。
那么在该activity中,如何操作呢?

seachEditText = (EditText) findViewById(R.id.my_chat_seach);
watchSearch();

然后

/**
     * @方法说明:监控软键盘的的搜索按钮
     * @方法名称:watchSearch
     * @返回值:void
     */
    public void watchSearch() {
        seachEditText.setOnEditorActionListener(new OnEditorActionListener() {

            @Override
            public boolean onEditorAction(TextView v, int actionId,
                    KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                    // 先隐藏键盘
                    ((InputMethodManager) seachEditText.getContext()
                            .getSystemService(Context.INPUT_METHOD_SERVICE))
                            .hideSoftInputFromWindow(ChatFriendsGroudSeach.this
                                    .getCurrentFocus().getWindowToken(),
                                    InputMethodManager.HIDE_NOT_ALWAYS);
                    // 搜索,进行自己要的操作...
                    seachList(viewIndex);//这里是我要做的操作!
                    return true;
                }
                return false;
            }
        });
    }

好的!完成!

Android一个界面实现标准体重计算器

%title插图%num
界面设计

<RelativeLayout xmlns:tools=”http://schemas.android.com/tools”
xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:paddingBottom=”@dimen/activity_vertical_margin”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
tools:context=”.MainActivity” >

<TextView
android:id=”@+id/textView2″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignLeft=”@+id/textView1″
android:layout_alignParentTop=”true”
android:layout_marginTop=”17dp”
android:text=”@string/hello_world” />

<TextView
android:id=”@+id/textView1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignParentLeft=”true”
android:layout_alignParentTop=”true”
android:layout_marginLeft=”18dp”
android:layout_marginTop=”68dp”
android:text=”@string/sex” />

<RadioGroup
android:id=”@+id/radioGroup1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignTop=”@+id/textView1″
android:layout_marginLeft=”25dp”
android:layout_toRightOf=”@+id/textView1″ >

<RadioButton
android:id=”@+id/radio0″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/sex1″ />

<RadioButton
android:id=”@+id/radio1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”@string/sex2″ />
</RadioGroup>

<TextView
android:id=”@+id/TextView01″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignLeft=”@+id/textView1″
android:layout_centerVertical=”true”
android:text=”@string/high” />

<EditText
android:id=”@+id/editText1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_centerVertical=”true”
android:layout_toRightOf=”@+id/TextView01″
android:ems=”10″
android:inputType=”number” >

<requestFocus />
</EditText>

<Button
android:id=”@+id/button1″
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_below=”@+id/editText1″
android:layout_marginTop=”72dp”
android:layout_toRightOf=”@+id/textView2″
android:onClick=”ClickHandler”
android:text=”@string/calculate” />

</RelativeLayout>

实现功能:

package org.wwj.calculate;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.Toast;

public class MainActivity extends Activity {

//定义各个组件
private Button cal;
private EditText high;
private RadioGroup sex;
private RadioButton sex1;
private RadioButton sex2;
private Double weight;
String sex3;

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//通过findViewById获取对象
cal=(Button) this.findViewById(R.id.button1);
high=(EditText) this.findViewById(R.id.editText1);
sex=(RadioGroup) this.findViewById(R.id.radioGroup1);
sex1=(RadioButton) this.findViewById(R.id.radio0);
sex2=(RadioButton) this.findViewById(R.id.radio1);

//设置Button事件监听
cal.setOnClickListener(new OnClickListener() {
public void onClick(View v) {

/*设置事件监听*/
sex.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {

//事件处理
if(checkedId==sex1.getId()){
weight=(Double.parseDouble(high.getText().toString()) – 80) * 0.7;
sex3=”男性”;
}else if(checkedId==sex2.getId()){
weight=(Double.parseDouble(high.getText().toString()) – 70) * 0.6;
sex3=”女性”;
}
}
});

/*Toast显示内容*/
Toast.makeText(MainActivity.this, “你是一位”+sex3+”\n”
+”你的身高是”+high.getText().toString()+”\n”+”你的标准体重是”+weight,
Toast.LENGTH_LONG).show();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}

————————————————

Android中实现滑动(上)—-基础知识

Android当中的滑动效果可以有效改善之前的点击,长按等UI体验,今天就来看一看其具体的实现,下面将会分为两个部分展开介绍,*部分介绍一下基础知识,第二部分介绍具体的几种实现方式。

1,Android中的坐标系
1.1 Android坐标系
如下图所示,Android中的坐标系是向右为X轴正方向,向下为Y轴正方向;其中(0,0)坐标对应于手机屏幕的左上角

%title插图%num

1.2 视图坐标系
如下图所示,其中(0,0)代表父布局的左上角(嵌套同理)

%title插图%num

2,触控事件
MotionEvent对于用户与界面的交互来说必不可少,当然随着语音识别等AI技术的成熟,这种依赖会降低,目前我们在做的机器人产品用户与机器人交互主要是通过ASR和TTS以及人脸识别等,界面更多的是展示功能。其中MotionEvent中定义了很多事件常量,主要如下:

MotionEvent.ACTION_DOWN
MotionEvent.ACTION_UP
MotionEvent.ACTION_MOVE
MotionEvent.ACTION_CANCEL
MotionEvent.ACTION_OUTSIDE
MotionEvent.ACTION_POINTER_DOWN
MotionEvent.ACTION_POINTER_UP
MotionEvent.ACTION_BUTTON_PRESS
MotionEvent.ACTION_HOVER_ENTER
MotionEvent.ACTION_HOVER_EXIT
MotionEvent.ACTION_HOVER_MOVE
MotionEvent.ACTION_MASK
MotionEvent.ACTION_POINTER_INDEX_MASK
MotionEvent.ACTION_POINTER_INDEX_SHIFT
MotionEvent.ACTION_BUTTON_RELEAS
等,我们只需要在获取控件的时候设置onTouchEvent(EventMotion event)即可根据event收到的事件类型来做业务处理

3,获取坐标
3.1 通过View
getLeft() //获取View的左边到其父布局左边距离

getTop() //获取View的上边到其父布局上边距离

getRight() //获取View的右到其父布局左边距离

getBottom() //获取View的底边到其父布局上边边距离

3.2 通过MotionEvent
getX() //点击事件位置距离控件自身左边的距离

getY() //点击事件位置距离控件自身上边的距离

getRawX() //点击事件位置距离控件屏幕左边的距离

getRawY() //点击事件位置距离控件屏幕上边的距离

4,示例代码和解释
4.1 代码
MainActivity代码:

package com.hfut.operationscrollpre;

import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

/**
* @author why
* @date 2018-8-18 15:15:13
*/
public class MainActivity extends AppCompatActivity {

private static final String TAG = “MainActivity”;
Button button;
LinearLayout linearLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.test_button);
linearLayout = findViewById(R.id.test_layout);

// ActionBar actionBar=getSupportActionBar();
// if(actionBar!=null){
// actionBar.hide();
// }

button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_DOWN”);
Log.d(“按钮:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“按钮:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“按钮:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_UP:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_UP”);
Log.d(“按钮:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“按钮:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“按钮:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_MOVE:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_MOVE”);
break;
case MotionEvent.ACTION_CANCEL:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_CANCEL”);
break;
case MotionEvent.ACTION_OUTSIDE:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_OUTSIDE”);
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_POINTER_DOWN”);
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_POINTER_UP”);
break;
case MotionEvent.ACTION_BUTTON_PRESS:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_BUTTON_PRESS”);
break;
case MotionEvent.ACTION_BUTTON_RELEASE:
Log.d(“按钮:”, “onTouch: MotionEvent.ACTION_BUTTON_RELEASE”);
break;
default:
break;
}
return true;
}
});

linearLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_DOWN”);
Log.d(“父布局:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“父布局:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“父布局:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_UP:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_UP”);
Log.d(“父布局:点击坐标(相对控件本身):”, “onTouch: x,” + event.getX() + “;y,” + event.getY());
Log.d(“父布局:点击坐标(相对上层布局):”, “onTouch: Left,” + v.getLeft() + “;Top,” + v.getTop() + “;Right,” + v.getRight()
+ “;Bottom,” + v.getBottom());
Log.d(“父布局:点击坐标(相对整个屏幕):”, “onTouch: X,” + event.getRawX() + “;Y,” + event.getRawY());
break;
case MotionEvent.ACTION_MOVE:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_MOVE”);
break;
case MotionEvent.ACTION_CANCEL:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_CANCEL”);
break;
case MotionEvent.ACTION_OUTSIDE:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_OUTSIDE”);
break;
case MotionEvent.ACTION_POINTER_DOWN:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_POINTER_DOWN”);
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d(“父布局:”, “onTouch: MotionEvent.ACTION_POINTER_UP”);
break;
default:
break;
}
return true;
}
});
}
}
activity_main.xml代码:

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout 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:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”
tools:context=”com.hfut.operationscrollpre.MainActivity”>

<LinearLayout
android:layout_marginLeft=”100px”
android:id=”@+id/test_layout”
android:orientation=”vertical”
android:gravity=”center”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<Button
android:text=”我是测试按钮”
android:id=”@+id/test_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</LinearLayout>

</LinearLayout>
4.2 测试结果
(1)点击屏幕

%title插图%num

 

(2)滑动屏幕

%title插图%num

 

(3)点击按钮

%title插图%num

(4)滑动按钮

%title插图%num

分析:

getX() + getLeft()+父布局到屏幕左侧距离=getRawX()

getY()+getTop()+父布局到屏幕上边距离=getRawY()

从日志来看我们的父布局(id为test_layout)到屏幕上边距离是118px,实际上我们并没有设置marginTop属性值,这里我们千万别忘了其父布局上面还有一个存放ActionBar的FrameLayout所占据的空间,还有就是我们平时喜欢使用dp单位来表示margin属性值,而这里获取的坐标单位都是px,所以在测试的时候需要注意,有时候我们设置了margin值为100dp,显示的结果大小很可能不是100px,这之间有一个单位换算。可以使用:

public static int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
获取,首先获取像素密度,然后在换算成对应的像素值;假如我们把上面的设置改为:

<LinearLayout
android:layout_marginLeft=”100dp”
android:id=”@+id/test_layout”
android:orientation=”vertical”
android:gravity=”center”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

<Button
android:text=”我是测试按钮”
android:id=”@+id/test_button”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content” />
</LinearLayout>
再次点击子布局,日志如下:

%title插图%num

再看看MainActivity中打印的日志:

Log.d(TAG, “onCreate: “+dp2px(getApplicationContext(),100));//代码

08-18 10:39:58.096 9114-9114/? D/MainActivity: onCreate: 133 //日志
————————————————

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速