yunshuipiao / swblog Goto Github PK
View Code? Open in Web Editor NEWmachine learning practitioner, android and python
License: MIT License
machine learning practitioner, android and python
License: MIT License
找到无序数组中的第k大元素, kth_Largest_element_in_an_array。
常见的解决方案有两种,快速排序**和堆排序**,简单记录一下过程,顺便复习快速排序和堆排序过程。
快速排序的理论基础是随机取一个数,大的放左边,小的放右边,完成一趟排序。
分别对左右两边递归做相同的分割,最后还剩一个数,自然有序,完成快速排序。
那么找第k大的元素,即完成一次排序后,随机找出的这个数的前面有 k -1个数,那么就是第k大个元素。
下标: k - 1 = index.
否则再根据下标关系判断在k在左边还是右边,继续如上操作。
快速排序完成一次排序的函数如下:
# 最后一个数为支点,从小到大排序
def partition_two(array, lo, hi):
i = lo
j = hi
while i < j:
while i < j and array[i] <= array[hi]:
i += 1
while i < j and array[j] >= array[hi]: # =保证支点不变,可以后续比较。
j -= 1
array[i], array[j] = array[j], array[i]
array[i], array[hi] = array[hi], array[i]
return i
下面得到分界点的下标,对左右两边递归分割,完成排序:
# 快速排序
def quick_sort(array, lo, hi):
#参数合法性
if array is None or lo >= hi:
return
mid = partition_two(array, lo, hi)
quick_sort(array, lo, mid - 1)
quick_sort(array, mid + 1, hi)
找第k大元素的代码类似。
首先堆不作介绍,分为大根堆和小根堆。堆的操作有插入和删除最大(小)元素,插入的节点放在最后,然后进行堆化操作。
堆的一个经典实现是完全二叉树,这样实现的堆为二叉堆。
下面是数组进行从小到大的堆排序过程。
###堆化数组
一个随机数组,对根节点i进行堆化操作(i的左右子树也都是堆)。
选择左右节点的小节点与根节点比较,若是根节点还小于较小值,则满足堆的性质。
否则进行交换,对交换过的节点继续进行堆化操作,直至全部堆化。
#对数组来说,以下标index为根节点的左右子树的下标为 index * 2 + 1, index * 2 + 2
# 将length个元素,i为节点的数组堆化(i的子节点也都是堆)
def big_heap_array(array, i, length):
# 左叶子节点
temp_value = array[i]
node_index = (i << 1) + 1
while node_index < length:
#左右叶子节点的较大值
if node_index + 1 < length and array[node_index + 1] > array[node_index]:
node_index = node_index + 1
# 如果根节点大于叶子节点,说明已经是堆,退出循环。
if array[node_index] < temp_value:
break
array[i] = array[node_index]
# 调整交换过元素的子节点
i = node_index
node_index = (i << 1) + 1
array[i] = temp_value
上面操作是堆化i下标节点的数组,从小到大的排序过程如下:
#n长度的数组,最后一个根节点的下标为: n / 2 - 1
#构建最大堆,从小到大排序
def heap_sort_one(array):
# 最后一个跟节点
last_node_index = (len(array) >> 1) - 1
for node_index in range(last_node_index, 0 - 1, -1): #[llast_node_index .. 0] 的列表
big_heap_array(array, node_index, len(array))
# 堆顶最大值和最后一个数互换, 然后堆化数组
for index in range(len(array) - 1, 0, -1):
array[0], array[index] = array[index], array[0]
big_heap_array(array, 0, index)
下面来解决此问题。
首先利用数组的前k个元素建立一个k大小的小根堆。逐次取后面的元素与堆顶比较, 如果大于堆顶,则替换堆顶,然后进行堆化。最终结果就是最大的前k个元素都在堆中,其中堆顶元素堆小,就是第k大的元素。
标签(空格分隔): react mobx Android
作为一个刚开始看react-native的小白,找到的源码我都看不太懂,还有涉及redux的知识。后面同事介绍mobx,因此记录一下学习过程。
过多的内容这里不做叙述,请看下面链接(可以知道是什么和为什么,很短)
如何理解 Facebook 的 flux 应用架构?
理解 React,但不理解 Redux,该如何通俗易懂的理解 Redux?
MobX vs Redux: Comparing the Opposing Paradigms - React Conf 2017 纪要
(对于redux,请参看Redux 入门教程(三):React-Redux 的用法)
务必多看几遍,下面开始。
安装好所需的环境。
选择一个目录,执行
react-native init FirstReact
cd FisrtReact
npm install
react-native run-adnroid
至此RN的demo可以正常启动。
安装mobx:
npm i mobx mobx-react --save
安装mobx相关的包
npm i babel-plugin-transform-decorators-legacy babel-preset-react-native-stage-0 --save-dev
安装一些 babel 插件,以支持 ES7 的 decorator 特性(后面有不用的方法, ES6)
然后打开 .babelrc 文件配置 babel 插件:
{
"presets": ["react-native"],
"plugins": [
"syntax-decorators",
"transform-decorators-legacy" ]
}
依赖安装完成。
在根目录下创建mobxDemo
文件夹。
新建AppState.js
文件:
import {action, observable, useStrict} from "mobx";
import {extendObservable} from "mobx";
class AppState {
@observable
timer = 101;
addTimers() {
this.timer += 1
}
resetTimer() {
this.timer = 0;
}
}
export default new AppState()
@observable
指明需要观察的对象(值,列表,数组,类等。)
其他的 action
, computed
可以后面去了解。
同目录下新建文件:MobxDemo.js
@observer
class App extends React.Component {
render() {
return (
<View>
<Text>当前的数是:{AppState.timer}</Text>
<Button
onPress={() =>
AppState.addTimers()}
title='add'
/>
<Button
onPress={() =>
AppState.resetTimer()
}
title='reset'
/>
</View>
);
}
}
export default App;
在需要观察的地方加@observer
。
修改index.js文件:
import { AppRegistry } from 'react-native';
import App from './mobx/MobxDemo';
AppRegistry.registerComponent('FirstReact', () => App);
刷新运行程序,完成对timer的加和重置。
在找资料的过程中,基本没有es6的相关实现。
中文文档:http://cn.mobx.js.org/
下面是ES6不带装饰器的写法:
AppState.js
import {action, observable, useStrict} from "mobx";
import {extendObservable} from "mobx";
class AppState {
constructor() {
let that = this;
extendObservable(this, {
timer: 11,
get tenTimer() {
return that.timer * 2
},
addTimers: action(function () {
this.timer += 1
}),
resetTimer: action( () => {
that.timer = 0
})
})
}
}
export default new AppState()
MobxDemo.js
import React from "react";
import {observer} from "mobx-react";
import {View, Text, Button} from "react-native";
import AppState from './AppState'
const App = observer( class MobxDemo extends React.Component {
render() {
return (
<View>
<Text>当前的数是:{AppState.tenTimer}</Text>
<Button
onPress={() =>
AppState.addTimers()}
title='add'
/>
<Button
onPress={() =>
AppState.resetTimer()
}
title='reset'
/>
</View>
);
}
})
export default App;
目前AlertDialog已经足够全面,包括简单,列表,多选列表对话框,可以满足大部分日常开发。
这里自定义一个对话框, 了解一下material design关于对话框的相关标准。
首先定义一个TextView
的扩展方法(非常建议, 可以获取context), 如下:
fun TextView.countDown() {
val frameLayout = FrameLayout(context)
val rootView = LayoutInflater.from(context).inflate(R.layout.view_count_down, null)
val mobileView = rootView.find<EditText>(R.id.vcd_et_mobile)
val codeView = rootView.find<EditText>(R.id.vcd_et_code)
val sendCode = rootView.find<TextView>(R.id.vcd_tv_send_code)
sendCode.setOnClickListener {
Timber.d("发送验证码")
}
val lp = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT)
lp.topMargin = dp2px(20f)
lp.bottomMargin = dp2px(20f)
lp.leftMargin = dp2px(24f)
lp.rightMargin = dp2px(24f)
rootView.layoutParams = lp
frameLayout.addView(rootView)
AlertDialog.Builder(context).setTitle("手机号码")
.setNegativeButton("取消", { _, _ ->
showToast("取消")
})
.setPositiveButton("确认", { _, _ ->
showToast("确认")
})
.setView(frameLayout)
.show()
}
布局UI如下:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/white">
<EditText
android:id="@+id/vcd_et_mobile"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:padding="10dp"
android:hint="请输入手机"
android:text=""
android:textSize="16sp"
/>
<EditText
android:id="@+id/vcd_et_code"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="请输入验证码"
app:layout_constraintTop_toBottomOf="@id/vcd_et_mobile"
app:layout_constraintRight_toLeftOf="@id/vcd_tv_send_code"
android:background="@color/white"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginTop="10dp"
android:layout_marginRight="10dp"
android:padding="10dp"
android:textSize="16sp"
/>
<TextView
android:id="@+id/vcd_tv_send_code"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/vcd_et_code"
app:layout_constraintBottom_toBottomOf="@id/vcd_et_code"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toRightOf="@id/vcd_et_code"
android:text="获取验证码"
android:background="@color/white"
android:textColor="@color/colorTextMain"
android:textSize="16sp"
android:gravity="center"
/>
</android.support.constraint.ConstraintLayout>
上面对应的几个点如下:
完成的UI如下:
假设对Rxjava2的使用有一定的了解,知道Observable, disposable, subscribe的简单使用。
首先定义一个Observable
和disposable
对象:
var disposable: Disposable? = null
val countTimesObservable = Observable.interval(1, TimeUnit.SECONDS)
.take(10)
.doOnDispose {
Logger.d(TAG, "取消订阅")
}
.doOnSubscribe {
Logger.d(TAG, "开始计时")
disposable = it
}.doOnComplete {
Logger.d(TAG, "结束计时")
sendCode.text = context.getString(R.string.login_get_code)
disposable?.let {
if (it.isDisposed) {
it.dispose()
}
}
}
.observeOn(AndroidSchedulers.mainThread())
这里简单解释一下几个方法的意思:
interval
表示每间隔1秒发送一个 Long
型数据;
take
表示取10个;
doOnDispose:表示当取消订阅(调用disposable.dispose()
)是产生的回调;
doOnSubscribe
:表示开始接受数据(调用countTimesObservable.subscribe()
)是的回调,这里将disposable赋值,以便后续调用。
doOnComplete
:表示结束时(倒计10s完成)的回调;
这里有个点需要注意:doOnDispose
一定要在 doOnSubscribe
之前声明
这几个方法主要是做现场的保留与恢复,体现链式的好处。
接下面是完成按钮的监听,即点击 发送验证码 时的功能:
sendCode.setOnClickListener {
countTimesObservable.subscribe {
val second = 9 - it
sendCode.text = "已发送($second)"
Timber.d(second.toString())
}
}
点击按钮开始倒计时,并修改显示文本,打印log如下:
04-14 14:50:57.915 28606-28606/com.swensun.swdesign D/ViewExtensionKt$countDown$countTimesObservable: 开始计时
04-14 14:50:58.922 D/ViewExtensionKt$countDown: 9
04-14 14:50:59.921 D/ViewExtensionKt$countDown: 8
04-14 14:51:00.921 D/ViewExtensionKt$countDown: 7
04-14 14:51:01.921 D/ViewExtensionKt$countDown: 6
04-14 14:51:02.919 D/ViewExtensionKt$countDown: 5
04-14 14:51:03.923 D/ViewExtensionKt$countDown: 4
04-14 14:51:04.922 D/ViewExtensionKt$countDown: 3
04-14 14:51:05.921 D/ViewExtensionKt$countDown: 2
04-14 14:51:06.922 D/ViewExtensionKt$countDown: 1
04-14 14:51:07.922 D/ViewExtensionKt$countDown$countTimesObservable: 结束计时
04-14 14:51:07.923 D/ViewExtensionKt$countDown: 0
04-14 14:51:07.924 D/ViewExtensionKt$countDown$countTimesObservable: 取消订阅
可以看到当点击按钮时开始倒计时,完成时调用onComplete完成现场恢复。
假如需要在倒计时中间停止怎么办:
设置dialog的监听, 取消订阅,修改代码如下:
AlertDialog.Builder(context).setTitle("手机号码")
.setNegativeButton("取消", { _, _ ->
showToast("取消")
disposable?.dispose()
})
.setPositiveButton("确认", { _, _ ->
showToast("确认")
disposable?.dispose()
})
.setView(frameLayout)
.show()
开始计时后点击取消,log如下:
04-14 14:57:04.190 28980-28980/com.swensun.swdesign D/ViewExtensionKt$countDown$countTimesObservable: 开始计时
04-14 14:57:05.198 28980-28980/com.swensun.swdesign D/ViewExtensionKt$countDown: 9
04-14 14:57:06.198 28980-28980/com.swensun.swdesign D/ViewExtensionKt$countDown: 8
04-14 14:57:07.197 28980-28980/com.swensun.swdesign D/ViewExtensionKt$countDown: 7
04-14 14:57:08.197 28980-28980/com.swensun.swdesign D/ViewExtensionKt$countDown: 6
04-14 14:57:09.116 28980-28980/com.swensun.swdesign D/ViewExtensionKt$countDown$countTimesObservable: 取消订阅
在倒计时到5时取消订阅,调用相关方法恢复现场。
这里作为一个Rxjava2的使用场景,相比使用Runnable来说,结构清晰明了,也是整个函数变的更纯,以后会陆续介绍使用Rxjava2的使用场景,方便开发。2
完成示例代码:
github:dialog countdown Rxjava2
关于纯函数,下面会介绍函数式编程的基本概念和方法, 以及柯里化的相关知识。
总结:
说明:本文章在上篇的基础上,代码在Android的环境中运行,当然可以单独写。
后面的文章就不跟Android代码联系,最后再回到Android, 写一个柯里化和函数式编程的例子。
上篇文章提到一下纯函数, 类似两个数的加法这样:
fun add1(a: Int, b: Int): Int {
return a + b
}
纯函数可以理解为一种 相同的输入必定有相同的输出的函数,没有任何可以观察到副作用
纯函数的输出只取决于输入,输入定责输出定。在笔者写Android的过程中,尽量使用纯函数,较少副作用。
关于上面的函数,可以写成如下的形式:
fun add2(a: Int, b: Int) = a + b
在函数式编程中,首先要介绍一下 高阶函数
高阶函数是将函数用作参数或者返回值的函数。Kotlin的Collection类型中有大量的这种高阶函数,例如:Iterable的filter, foreach函数。本篇文章后面也可写相应的高阶函数。
现在开始,定义一个函数,将上面的add1
当做参数执行计算。
代码如下:
fun calculate(a: Int, b: Int, f: (Int, Int) -> Int) : Int {
return f(a, b)
}
如上, 定义了一个calculate
函数, 该函数接收三个参数, 整形的a,b和作为函数的f。这比较特别,看参数定义可以知道,函数f将两个Int型值作为输入,输出一个Int值。可以进行如下调用:
codeBtn.setOnClickListener {
when (position) {
0 -> {
codeBtn.countDown()
}
1 -> {
val result = calculate(2, 3, ::add1)
Timber.d("计算结果为: $result")
}
}
}
上述是RecyclerView的部分代码,这里只关注1的情况,将(2,3,::add1)传入,add1前有双冒号表示当做函数参数传入。输入结果如下:
D/CodeExampleActivity$ItemViewHolder$bindData: 计算结果为: 5
当然也可以去除双冒号:既然函数可以当做参数或者返回值,那么相应的可以将函数理解为对象,
定义一个函数对象,执行如上相同的操作:
val add3 = {a: Int, b: Int -> a + b}
Timber.d(add3(3, 5).toString())
val result = calculate(2, 3, add3)
Timber.d("计算结果为: $result")
log打印如下:
D/CodeExampleActivity$ItemViewHolder$bindData: 函数对象计算结果:8
D/CodeExampleActivity$ItemViewHolder$bindData: 计算结果为: 5
相比起来,我觉得当做返回值要难一些,慢慢来:
fun add4(): (Int, Int) -> Int {
return ::add2
}
fun add5() = {
add3
}
val add6 = add3
首先做如上函数声明,函数add2 和函数对象 add3 用作返回值。
因为add2是函数,所以有双冒号;add3是对象,特殊在其是一个函数。
那么怎么去调用呢:
val addF = add4()
val addFF = add5()
Timber.d("函数add4(): ${add4()}")
Timber.d("函数对象addF: $addF")
Timber.d("函数add5(): ${add5()}")
Timber.d("函数对象addF计算结果:${addF(1, 2)}")
Timber.d("函数add4()计算结果: ${add4()(3, 4)}")
Timber.d("函数addFF计算结果: ${addFF()(4, 5)}")
Timber.d("函数add5()计算结果: ${add5()()(6, 7)}")
log日志打印如下:
D/CodeExampleActivity$ItemViewHolder$bindData: 函数add4(): fun add2(kotlin.Int, kotlin.Int): kotlin.Int
D/CodeExampleActivity$ItemViewHolder$bindData: 函数对象addF: fun add2(kotlin.Int, kotlin.Int): kotlin.Int
D/CodeExampleActivity$ItemViewHolder$bindData: 函数add5(): () -> (kotlin.Int, kotlin.Int) -> kotlin.Int
D/CodeExampleActivity$ItemViewHolder$bindData: 函数对象addF计算结果:3
D/CodeExampleActivity$ItemViewHolder$bindData: 函数add4()计算结果: 7
D/CodeExampleActivity$ItemViewHolder$bindData: 函数addFF计算结果: 9
D/CodeExampleActivity$ItemViewHolder$bindData: 函数add5()计算结果: 13
在以上kotlin代码中有一个小插曲:
log打印中出现提示: Kotlin reflection is not available
解决方案:kotlin-reflection-is-not-available
在build.gradle
中添加 implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
对log输出稍做解释,不过我相信没有介绍也能看懂。
函数add4() 对象addF都是 函数add2(), log中有参数和返回值信息。
而add5()是对象add3, 是一个对象,接收0个参数返回一个函数,这个函数接收两个参数返回一个值。
根据log可以简单的了解其用法,后面会介绍。
进一步改写:
fun add7(): (Int, Int) -> Int {
return fun(a: Int, b: Int): Int {
return a + b
}
}
fun add8(): (Int, Int) -> Int {
return {a: Int, b: Int -> a + b}
}
val add9 = {a: Int, b: Int -> a + b}
这里我们直接定义匿名函数当做函数值。同样看一下log打印输出:
val addF = add7()
val addFF = add8()
Timber.d("函数add7(): ${add7()}")
Timber.d("函数对象addF: $addF")
Timber.d("函数add8(): ${add8()}")
Timber.d("函数addFF: $addFF")
Timber.d("函数add6(): $add9")
Timber.d("函数对象addF计算结果:${addF(1, 2)}")
Timber.d("函数add7()计算结果: ${add7()(3, 4)}")
Timber.d("函数addFF计算结果: ${addFF(4, 5)}")
Timber.d("函数add8()计算结果: ${add8()(6, 7)}")
log输出:
D/CodeExampleActivity$ItemViewHolder$bindData: 函数add7(): (kotlin.Int, kotlin.Int) -> kotlin.Int
D/CodeExampleActivity$ItemViewHolder$bindData: 函数对象addF: (kotlin.Int, kotlin.Int) -> kotlin.Int
D/CodeExampleActivity$ItemViewHolder$bindData: 函数add8(): (kotlin.Int, kotlin.Int) -> kotlin.Int
D/CodeExampleActivity$ItemViewHolder$bindData: 函数addFF: (kotlin.Int, kotlin.Int) -> kotlin.Int
D/CodeExampleActivity$ItemViewHolder$bindData: 函数add9: (kotlin.Int, kotlin.Int) -> kotlin.Int
D/CodeExampleActivity$ItemViewHolder$bindData: 函数对象addF计算结果:3
D/CodeExampleActivity$ItemViewHolder$bindData: 函数add7()计算结果: 7
D/CodeExampleActivity$ItemViewHolder$bindData: 函数addFF计算结果: 9
D/CodeExampleActivity$ItemViewHolder$bindData: 函数add8()计算结果: 13
这里不做解释,相信你也能看懂了。
其中有个地方需要注意,即add5()函数的定义为什么是:
函数add5(): () -> (kotlin.Int, kotlin.Int) -> kotlin.Int
:
这里我再写两个函数说明一下:
val add5_1 = { 10 }
val add5_2 = 10
fun add5_1() = {
10
}
fun add5_2(): Int {
return 10
}
这里要搞清楚对象和函数的区别就简单。
当返回或者等于{}
时,它就是一个函数, 调用后返回最后一个对象。
val addF = add5_1
val addFF = add5_2
val addFFF = add5_1()
val addFFFF = add5_2()
Timber.d("$addF")
Timber.d("${addF()}")
Timber.d("$addFF")
Timber.d("$addFFF")
Timber.d("$addFFFF")
输出如下:
D/CodeExampleActivity$ItemViewHolder$bindData: () -> kotlin.Int
D/CodeExampleActivity$ItemViewHolder$bindData: 17
D/CodeExampleActivity$ItemViewHolder$bindData: 10 D/CodeExampleActivity$ItemViewHolder$bindData: () -> kotlin.Int D/CodeExampleActivity$ItemViewHolder$bindData: 10
那么假如返回的最后一个不是数怎么办,当然,它可以是一个函数。
如下演示:
val add5_3_1 = {
val temp = 10
{a: Int -> a + temp}
}
fun add5_3_2() = {
val temp = 10
{a: Int -> a + temp}
}
首先是add5_3_2()是一个函数,其返回值也是一个函数,该函数完成+10
的功能。
如下调用:
Timber.d("${add5_3_1()(15)}")
Timber.d("${add5_3_2()()(15)}")
现在应该能简单理解并知道结果为 25 了吧。
以上就是介绍将函数作为输入和输出的简单介绍。当然,我上面的例子由于过于浅显和简单,容易混淆,实际使用过程中也不会如上定义函数, 不过慢慢会理解的。
接下来就可以继续上述的一个事情,针对collection写自己的高阶函数。
下篇文章见
完整代码查看:
github: 纯函数 高阶函数 函数当做输入输出
首先下载mongo
镜像,简单命令不做过多叙述,前面文章有介绍怎么基本使用docker。
之后编写docker-compose.yml
文件,运行 docker-compose up -d
生成容器并后台启动。
// docker-compose.yml
// command:启动是的命令行参数,添加认证auth
version: '2'
services:
mongodb:
image: mongo
ports:
- 27017:27017
volumes:
- "./data/configdb:/data/configdb"
- "./data/db:/data/db"
command: mongod --auth
tty: true
运行docker ps
查看容器是否运行。
进入docker容器并进入mongo命令行。
docker exec -it 4 /bin/bash
mongo
此时show dbs
无法执行,需要认证。
切换到admin
并创建root用户:
use admin
db.createUser({ user: 'root', pwd: 'root', roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] })
如上,可以看到root用户创建成功。exit
退出mongo命令行,带验证的mongodb已经创建成功。
接下来创建普通用户,并演示验证。
再次执行mongo
进入mongodb命令行。
可以看到root用户验证成功,并且可以查看数据库。
下面创建普通用户,和创建root用户基本一致,只是角色不同 。
//拥有对数据库app的读写权限。
use app
db.createUser(
{
user: "swen",
pwd: "swen",
roles: [ { role: "readWrite", db: "app" }
]
}
)
创建成功并exit
退出,swen用户可以对(只能对)app进行操作。
下面做基本演示。
基本验证:
演示往 test
集合插入简单数据,并查看数据库状态。
介绍到此完毕。
参考资料:
MongoDB 用户名密码登录
MongoDB 常用基本命令
github: https://github.com/yunshuipiao
标签(空格分隔): googlesamples 知乎
研究官方文档,是通往业界大拿的途径-----不知道是谁。
这是一个新的系列,打算看一些 github 上谷歌官方的小例子,好处不必多说。
最近的项目中,所有的布局已经换成了 constraintlayout
,这是谷歌推荐的布局。
因此打算从 constraintlayoutexamples
入手。
一点小技巧:视图编辑器用来辅助,布局还是得手写代码。其中 tools
开头的属性是负责在视图编辑器辅助展示我们的布局,手写布局可以忽略。
基本知识不做过多介绍,说一些我的收获。
关于 view 的居中:
用的最多的是约定上下左右即可,通过 margin 设置偏移量。
若是要求图中的 button2
相对 button1
的底部居中, 相对 centeredbutton
的右边居中:
则设置 button2
的 top
, bottom
与 button1
top
对齐,右边类之。
实现下图的居中对齐:
中间按钮先居中对齐,然后是左右。
layout_constraintDimensionRatio
属性:
设置空间的宽高比,默认宽:高。官方中可以通过指定:“h, 16:9”
设成高:宽(有待确认)。注意:不固定的一方的值设为0dp
。
关于guideline
,特别好用,其中:
app:layout_constraintGuide_percent
:用百分比设置偏移量;
app:layout_constraintGuide_begin
: 具体数值设置偏移量。
与之不同的属性:app:layout_constraintHorizontal_bias="0.5"
通过百分比设置 view
的偏移量
链条(chains
):相互引用, 可以设置偏移量,方便统一管理。
其中 layout_constraintVertical_chainStyle
属性在链条的头部设置。
三个值:
packed
:链条控件紧凑
spread
:链条控件均匀分布。
spread_inside
:链条内部控件均匀分布
通过 constraintset
设置动画
参考资料constraintlayout动画
我的总结:不要盲目使用,根据需求综合使用 constraintlayout
,搭配 linelayout
。
使用视图编辑器坐辅助,手写布局。
欢迎加微信youquwen1226与我讨论。
笔者自16年毕业至如今,开发 ios
三个月,Android
七个月。自觉学习的知识严重不足,但偶尔记点开发的心得。因此打算整理一下这些笔记,月为单位,特此记录。
2016.7 -- 2016.9 ios
开发时期三个月,暂且不表。
###2017.10
开始熟悉项目源码,几个学习的关键点:handle
, gradle
, 匿名内部类和泛型, adapter
, Layoutinflater.inflate
其中项目用到的Kvo绑定是借鉴ios的一套框架,可以使用 databinding
加以替换。
反射机制, AppBarLayout
关于Android Drawable的微技巧阅读
几个要点:
drawable
文件夹存放图片文件,.png
, .jpg
. .9.png
, selector
的xml
文件,而mipmap
只用来存放应用程序的图标。drawable-xxhdip
。//判断2的幂次方
if ((n & -n) == n) // ture
后面阅读了stormzhang的博客,Android学习之路
里面涉及到的书籍推荐,第一行代码,疯狂Android讲义, Thinking In Java中文版,Effective java中文版,我也七七八八看过。此外,还涉及到android基础的文章推荐,Android activity生命周期, 四大组件的基本介绍,Intent
, 屏幕适配。中级知识:view
, Gson
, 布局优化(inculde
重用,merge
, viewstub
), 异步消息处理。 进阶知识:gradle
, 性能优化,以及兼容库,必备的开源库等,非常值得一看。
awesome-android-tips:常用的android代码。
onresume
是activity
获得用户焦点,在与用户交互。
onstart
是activity
用户可见,包括有一个activtity或者view在其上面,但没有完全覆盖,用户可以看到但不能与其交互。
参考文章:Android任务和返回栈
有关任务栈:涉及到几种启动模式:
任务是一个antivity
的集合, 使用栈来管理其中的activity
, 也被成为返回栈(back task
)。
当任务处于后台,返回栈中的所有activity
都会进入停止状态。
默认的任务和activity
的行为
activity A
启动 B时,A进入停止状态,系统保留相关信息(滚动位置,文本框输入内容等)。如果用户在B中按下back
, A重新回到运行状态。Home
, 该任进入后台,返回栈中所有activity
进入停止状态而且状态被保留。重新打开时将后台任务直接到前台, 恢复最顶端activity
。back
, 最顶端的Activity
从返回栈移除并被销毁,前面的activity
处于栈顶并活动。activity
都可以被实例化很多次,即使在不用的任务中。通过在manifest
文件设置元素的属性,或者在启动activity
是配置Intent的flag
来实现。
注意以下几个intent
的flag
。
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
使用manifest
文件:launchMode
属性来制定
standard:默认启动,每次启动创建实例,并放入当前任务栈。
singleTop:若将启动act在当前任务栈中存在且处于栈顶, 不会创建实例,而是调用栈顶act
的onNewIntent()
方法。这种方式启动的act
可被实例化多次,一个任务也可包含多个实例
singleTask:系统创建一个新的任务,并将此act放入新任务栈底。如果现有任务已存在该act的实例, 系统不会再创建,而是直接调用onNewIntent()
。此模式在同一任务中只会存在一个实例。(此模式的启动act
都是指其他程序的act
, 才会创建新任务)
singleInstance:与singleTask
类似,但不会向act所在的任务中再添加其他act
。
使用Intent flags
在startActivity时,为intent
加入flag
来改变act
与任务的关联方式。
FLAG_ACTIVITY_NEW_TASK:新启动act
被放入新任务(启动其他程序),模拟launcher
。
FLAG_ACTIVITY_CLEAR_TOP:若将启动act
已存在,不会再创建,而是关闭此act
之上的所有act
。
FLAG_ACTIVITY_SINGLE_TOP:与singletop
的效果一样。
Binder
和socket
实现。实现Serializable
,Pracalable
来持久化数据,完成对象的序列化,后者安卓主推,但是将对象序列化到存储设备或者对像序列化后通过网络传输稍显麻烦,所以选择使用。
接上篇文章,其简单介绍了纯函数,高阶函数,写了几个简单将函数当做输入输出的例子。
现在来自定义我们的高阶函数。
首先来看最简单的forEach:
fun main(args: Array<String>) {
val list = arrayListOf(1, 4, 7)
list.forEach { println(it) }
// 1, 4, 7
}
看一下源码
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
简单解释:这里用到了泛型,简单忽略。
forEach中的action就是一个函数,类似我们上面的println()
函数执行也好理解:循环每一个元素执行action。
fun main(args: Array<String>) {
val list = arrayListOf(1, 4, 7)
list.forEach { print1(it) }
list.forEach { print2(it) }
list.forEach({ print1(it) })
list.forEach(print2)
list.forEach(print3)
}
fun print1(a: Int) {
print(a)
}
val print2 = { a: Int ->
print(a)
}
val print3 = ::print1
上述5个forEach的输出相同,在上篇文章的基础上,相信理解不难。
接下来看一下:filter:
val list = arrayListOf(1, 4, 7)
list.filter { it != 1 }.forEach { println(it) } // 4, 7
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this) if (predicate(element)) destination.add(element)
return destination
}
filter函数: 根据给定的高阶函数对元素进行判断后的Boolean返回值来过滤,返回新的迭代器。
稍微花5分钟理解一下。
接下来定义一个Collection的扩展函数,对每个元素指定 +2 功能:
fun main(args: Array<String>) {
val list = arrayListOf(1, 4, 7)
list.add2().forEach { println(it) } // 3, 6, 9
}
fun Iterable<Int>.add2(): List<Int> {
val tempList = arrayListOf<Int>()
for (e in this) {
tempList.add(e + 2)
}
return tempList
}
没有用到泛型, 非常容易理解,对Collection中的每个值+2。
当然,可以把2配置成参数:
fun main(args: Array<String>) {
val list = arrayListOf(1, 4, 7)
list.add2(3).forEach { println(it) }
}
fun Iterable<Int>.add2(a: Int): List<Int> {
val tempList = arrayListOf<Int>()
for (e in this) {
tempList.add(e + a)
}
return tempList
}
上面看过的源码中,inline是内联的意思,针对lambda优化,表示调用这个函数时,将代码复制到调用的位置,而不经过函数的调用栈, 节省开销。
下面在此基础上改一下,加上lambda。
inline fun Iterable<Int>.calculate(f: (Int) -> Int): List<Int> {
val tempList = arrayListOf<Int>()
for (e in this) {
tempList.add(f(e))
}
return tempList
}
定义一个扩展函数,同样也是完成加2的功能:
fun main(args: Array<String>) {
val list = arrayListOf(1, 4, 7)
list.calculate { add2(it) }.forEach { println(it) }
list.calculate { add3(it) }.forEach { println(it) }
list.calculate(::add2).forEach { println(it) }
list.calculate(add3).forEach { println(it) }
list.calculate { it + 2 }.forEach { println(it) }
}
fun add2(a: Int): Int {
return a + 2
}
val add3 = {a: Int -> a + 2}
inline fun Iterable<Int>.calculate(f: (Int) -> Int): List<Int> {
val tempList = arrayListOf<Int>()
for (e in this) {
tempList.add(f(e))
}
return tempList
}
上面有5中函数的调用演示,其结果都一样,现在应该非常容易理解。
回过头来看文章最前面定义的高阶函数:
fun Iterable<Int>.add2(): List<Int> {
val tempList = arrayListOf<Int>()
for (e in this) {
tempList.add(e + 2)
}
return tempList
}
编译器有智能转换的功能, 转换后代码如下:
fun Iterable<Int>.add2(): List<Int> {
val tempList = this.map { it + 2 }
return tempList
}
此处看到了map,结合上面定义calculate函数,接收一个函数完成类似的功能,此时去看map的源码应该非常容易理解。
此时还可以继续只能转换:
fun Iterable<Int>.add2(): List<Int> {
return map { it + 2 }
}
此时对比如下调用:
fun main(args: Array<String>) {
val list = arrayListOf(1, 4, 7)
list.add2().forEach { println(it) }
list.map { it + 2 }.forEach { println(it) }
list.map(::add2).forEach { println(it) }
}
fun add2(a :Int): Int {
return a + 2
}
结果一样,不过过多解释。至此,相信应该不难写出自己的高阶函数。
柯里化是一个数学概念, 简单的来说,就是对于一个有多个参数的函数,转换成每次只接受一个参数的函数,最后输入结果。
Currying 的重要意义在于可以把函数完全变成「接受一个参数;返回一个值」的固定形式。
看代码,从简单开始:
首先定义一个扩展方法执行打印输出:
fun <T> T?.println() = kotlin.io.println(this)
首先是一个加法函数:
fun main(args: Array<String>) {
add(1, 2).println()
}
fun add(a: Int, b: Int): Int {
return a + b
}
这是最简单的调用方法,柯里化后希望变成如下调用:
add(1)(2).println()
修改后的代码如下:
fun main(args: Array<String>) {
add(1, 2).println()
add1(1)(2).println()
add2(1)(2).println()
add3(1)(2).println()
}
fun add(a: Int, b: Int): Int {
return a + b
}
fun add1(a: Int): (Int) -> Int {
return {b: Int -> a + b}
}
fun add2(a: Int) = {b: Int -> a + b}
val add3 = {a: Int -> {b: Int -> a + b}}
全部打印的结果输出都是为3,其在函数和对象之间有略微的区别,也容易理解。
val addOne = add1(1)
addOne(2).println()
addOne(4).println()
另外还可以如此调用, addOne是一个函数对象,接收一个函数完成+1的功能。
偏函数:对一个多参数的函数,通过指定其中的一部分参数后得到的仍然是一个函数,那么这个函数就是原函数的一个偏函数了。
偏函数与 Currying 有一些内在的联系,如果我们需要构造的偏函数的参数恰好处于原函数参数的最前面,那么我们是可以使用 Currying 的方法获得这一偏函数的;当然,如果我们希望得到任意位置的参数被指定后的偏函数,后面介绍方法。
加入我要完成3个参数的加法怎么办,简单写一下:
fun main(args: Array<String>) {
add(1, 2, 3).println()
add1(1)(2)(3).println()
}
fun add(a: Int, b: Int, c: Int): Int {
return a + b + c
}
fun add1(a: Int): (Int) -> (Int) -> Int {
return fun(b: Int): (Int) -> Int {
return {c: Int -> a + b + c}
}
}
如下,完成了3个参数相加的currying。加入随着参数的增多,会变得越来越复杂。
我们可以定义一个 函数的扩展函数来进行函数currying:
fun main(args: Array<String>) {
::add.curried()(1)(2)(3).println() // 6
}
fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.curried()
= fun(p1: P1) = fun(p2: P2) = fun(p3: P3) = this(p1, p2, p3)
fun <P1, P2, R> Function2<P1, P2, R>.curried()
= fun(p1: P1) = fun(p2: P2) = this(p1, p2)
fun add(a: Int, b: Int, c: Int): Int {
return a + b + c
}
不容易理解, 看一下Function3的定义,
/**
* A functional interface (callback) that computes a value based on multiple input values.
* @param <T1> the first value type
* @param <T2> the second value type
* @param <T3> the third value type
* @param <R> the result type
*/
public interface Function3<T1, T2, T3, R> {
/**
* Calculate a value based on the input values.
* @param t1 the first value
* @param t2 the second value
* @param t3 the third value
* @return the result value
* @throws Exception on error
*/
@NonNull
R apply(@NonNull T1 t1, @NonNull T2 t2, @NonNull T3 t3) throws Exception;
}
这里不理解也没关系,记住currying怎么写,后面慢慢理解的。
上面有简单提到, 如果我们希望得到任意位置的参数被指定后的偏函数,也需要定义 函数的扩展方法, 如下:
fun <P1, P2, R> Function2<P1, P2, R>.partial1(p1: P1) = fun(p2: P2) = this(p1, p2)
fun <P1, P2, R> Function2<P1, P2, R>.partial2(p2: P2) = fun(p1: P1) = this(p1, p2)
目前kotlin标准库中没有实心,所以这里要定义两个方法,这两个方法分别用来生成两个参数分别被指定后的偏函数。
如下使用:
fun main(args: Array<String>) {
val addOne = add.partial1(1)
val addTwo = add.partial2("2")
addOne("2").println()
addTwo(3).println()
}
val add = {a:Int, b: String -> "$a$b"}
非常容易的能看出区别
至此,函数柯里化和偏函数基本介绍完了,本来还想写一个Android的具体例子, 限于篇幅,后面再补。
总结:
本篇文章和上一篇结合紧密,建议一起阅读。
前面文章讲到怎么提取动态网页的全部内容。接下来返回文章一,怎么登录并且保存登录状态,以便带上cookies下次访问。
if __name__ == '__main__':
url = 'https://www.zhihu.com/collection/146079773'
res = requests.get(url, verify=False)
resSoup = BeautifulSoup(res.content, 'lxml')
items = resSoup.select("div > h2 > a")
print(len(items))
verify=False
:取消ssl的验证。
运行这段代码, 输出结果未0, 粘贴该网页到一个没有登录知乎的浏览器打开,重定向到登录页, 说明需要登录。
验证:
if __name__ == '__main__':
url = 'https://www.zhihu.com/collection/146079773'
# res = requests.get(url, verify=False)
driver = webdriver.Chrome()
driver.get(url)
driver.implicitly_wait(2)
res = driver.page_source
resSoup = BeautifulSoup(res, 'lxml')
items = resSoup.select("div > h2 > a")
print(len(items))
执行代码,打开浏览器,显示知乎登录页,说明访问收藏夹需要登录。
登录技巧:
使用selenium打开登录页,设定延时时间(比如60s),手动输入账号密码登录知乎,60秒之后保存cookies到本地,完成登录。后续请求携带保存的cookie进行的登录。如果cookies过期,则简单重复这一步骤。
下面是详细步骤:
if __name__ == '__main__':
ssl._create_default_https_context = ssl._create_unverified_context
# url = 'https://www.zhihu.com/collection/146079773'
url = "https://www.zhihu.com/signin"
# res = requests.get(url, verify=False)
driver = webdriver.Chrome()
driver.implicitly_wait(5)
driver.get(url)
time.sleep(40)
cookies = driver.get_cookies()
pickle.dump(cookies, open("cookies.pkl", "wb"))
print("save suc")
执行这段代码,看是否有cookies.pkl文件生成, 成功保存了cookies。
接下来用第二段代码去验证。
if __name__ == '__main__':
cookies = pickle.load(open("cookies.pkl", "rb"))
url = 'https://www.zhihu.com/collection/146079773'
driver = webdriver.Chrome()
driver.get("https://www.zhihu.com/signin")
for cookie in cookies:
print(cookie)
driver.add_cookie(cookie)
driver.get(url)
driver.implicitly_wait(2)
res = driver.page_source
resSoup = BeautifulSoup(res, 'lxml')
items = resSoup.select("div > h2 > a")
print(len(items))
打开浏览器, 加载任意网页,接着加载cookies, 打开给定的url。运行代码,
如上,看到打印的cookies和提取的10个标题, 打开浏览器,页面不是登录页,说明登录成功。看cookies的有效时间。即可知道下次cookies的替换时间。
至此,最难定义的动态网页和登录问题已经解决。
下面就是怎么保存抓到的数据。
我的想法是先将需要登录的10页中所有问题和问题链接提取出来,保存为json文件以后后续处理。接着对每一个问题下的所有图片链接提取,保存或者直接下载就看个人选择了。
settings.py
文件中的中间键,DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
# 'zhihu.middlewares.PhantomJSMiddleware': 100,
}
反爬虫策略:
对于访问过快,网页一般会静止访问或者直接封ip。因此对于需要登录的爬虫来说,限制访问速度,比如5秒/次, 或者每个ip每分钟最大访问次数。对于不需要登录的页面来说,使用代理ip是最好的选择,或者降低访问次数都是可行的办法。
settings.py
文件的设置,
# Configure maximum concurrent requests performed by Scrapy (default: 16)
# CONCURRENT_REQUESTS = 32
# Configure a delay for requests for the same website (default: 0)
# See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 2
# The download delay setting will honor only one of:
# CONCURRENT_REQUESTS_PER_DOMAIN = 16
# CONCURRENT_REQUESTS_PER_IP = 16
这几个选项都是控制访问速度的,一般我设置DOWNLOAD_DELAY
即可,即每两秒访问一次。
执行代码如下:
class Zhihu(scrapy.Spider):
name = "zhihu"
cookeis = pickle.load(open("cookies.pkl", "rb"))
urls = []
questions_url = set()
for i in range(1, 11):
temp_url = "https://www.zhihu.com/collection/146079773?page=" + str(i)
urls.append(temp_url)
def start_requests(self):
for url in self.urls:
request = scrapy.Request(url=url, callback=self.parse, cookies=self.cookeis)
yield request
def parse(self, response):
print(response.url)
resSoup = BeautifulSoup(response.body, 'lxml')
items = resSoup.select("div > h2 > a")
print(len(items))
for item in items:
print(item['href'])
self.questions_url.add(item['href'] + "\n")
@classmethod
# 信号的使用
def from_crawler(cls, crawler, *args, **kwargs):
print("from_crawler")
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_closed)
return s
def spider_opened(self, spider):
print("spider close, save urls")
with open("urls.txt", "w") as f:
for url in self.questions_url:
f.write(url)
可以看到,成功抓取了44个链接,去除people, zhuanlan等几个无效链接,
后面即可从该文件读取内容,拼接链接,利用selenium做中间键提取所有的图片链接。
总结:这本文章讲了如何利用selenium去手动登录网站,保存cookies,以后后续登录(几乎可以登录所有的网站,限制访问速度避免被封)。
这三篇文章讲解了怎么使用scrapy去抓取想要的东西。现在无需使用框架,也可以涉及实现自己的爬虫。对于怎么保存图片,使用代理,后面会做简单介绍。
后面会写一篇怎么将爬虫部署在服务器上,利用docker搭建python环境去执行爬虫。
weixin:youquwen1226
github:https://github.com/yunshuipiao
欢迎来信探讨。
上篇回顾:kotlin和android architecture Component,Room的完成与测试。
接上篇,如果看到模拟的数据插入,那么可以继续往下看
该架构提供了一种管理activity和fragmeng生命周期的方法,并且让接下来介绍的livedata可以在有效生命周期内收到观察,更新UI。首先来看:
因为还是测试版的原因,目前可以继承lifecycleActivity和lifecyclefragment来实现。
class StoryListFragment : LifecycleFragment() {}
说一下类的继承结构:
lifecycleActivity(LifecycleFragment)实现LifecycleRegistryOwner接口。只有一个方法:
//源码
public interface LifecycleRegistryOwner extends LifecycleOwner {
@Override
LifecycleRegistry getLifecycle();
}
在activity或fragmeng中可以调用getLifecycle(),可以得到LifecycleRegistry,获取当前ui的状态。
可以做个测试:
自定义一个MainObserve:
class MainObserve : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
internal fun start() {
Log.d(TAG, "start: " + "init")
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
internal fun create() {
Log.d(TAG, "create: " + "init")
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
internal fun stop() {
Log.d(TAG, "stop: " + "init")
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
internal fun destory() {
Log.d(TAG, "destory: " + "init")
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
internal fun resume() {
Log.d(TAG, "resume: " + "init")
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
internal fun pause() {
Log.d(TAG, "pause: " + "init")
}
companion object {
private val TAG = "MyObserve"
}
}
然后在ui中使用lifeRegister.addObserver(MainObserve())
可以得到结果。
推荐看一下这几个接口和类的源代码,都非常简单。
上面都不是重点。
该架构与其他架构类似,提供viewmodel来分离UI和数据的处理。之前有部分的数据在UI中处理,UI层会变得臃肿,并且难以修改和重构。
以我的例子说明:
自定义的viewmodel需要继承ViewModel。如果需要用到application做系统相关的事情,可以继承AndroidViewModel(也可以用全局单例提供)。
class StoryListViewModel(val app: Application): AndroidViewModel(app) {}
这时,可以在activity使用var viewModel = ViewModelProviders.of(activity).get(StoryListViewModel::class.java)
得到viewmodel的实例,处理数据,联系UI。
注意:经笔者测试,如果使用var viewModel = StoryListViewModel()
new 出实例也是可以的,此时还可以带参数,方便很多。按我的理解,区别在于:
当然:viewModel不推荐带参数
前者可以保持一个单例,使用情况多个fragmeng在同一个activity中,利用viewmodel可以保存数据,多个fragmeng共享数据。但是,参数app必须是Application,在viewModel中做强制转换。
(app as ZhiJokeApplication)
.如果是ZhiJokeApplication
,编译会错误。
livedata和viewmodel和结合在一起的。livedata时一个可以感受UI生命周期的组件,不会造成内存泄漏。根据最佳实践,viewmodel处理数据逻辑,但并不涉及数据获取(db, net)。该架构推荐repository来获取保存数据。
与上一篇文章结合,自定义一个repository:
//使用了Dagger2, 不懂可以跳过。后面会写文章介绍。
//简单理解;等同于new, 创建实例
class StoryRepository @Inject
constructor(var db: AppDatabase) {
companion object {
var TAG = "StoryRepository"
}
fun insertStories(storys: List<Story>) {
Flowable.just(storys)
.observeOn(Schedulers.io())
.subscribe {
db.beginTransaction()
try {
db.storyDao().insertStories(storys)
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}
}
fun loadAllStories(): LiveData<List<Story>> {
var result = db.storyDao().loadAllStoriesLiveData()
return result
}
fun simlutateInsertData() {
var list = ArrayList<Story>()
for (i in 1..20) {
var s = Story()
s.id = (i)
s.data = ((i * i).toString() + "--" + i.toString())
s.displayData = ((i * i).toString() + "--" + i.toString())
s.title = ((i * i).toString() + "--" + i.toString())
s.image = ((i * i).toString() + "--" + i.toString())
list.add(s)
}
insertStories(list)
}
}
该类有三个方法,模拟插入数据和读取数据。
在viewmodel定义以下数据:
var storyList = MediatorLiveData<List<Story>>()
@Inject
//实例化StoryRepository
lateinit var storyRes: StoryRepository
关于livedata:有三个类,
我的理解:
之所以声明为MediatorLiveData,是因为story的来源可以有多种,网络,数据库。如果livedata的话,则网络获取的数据就不能赋值给livedata。如果MutableLiveData,普通list可以赋值给MutableLiveData。此时有一个问题,若数据库有变(插入数据), 则loadAllStories的返回结果时livedata,无法通过
MutableLiveData.postvalue赋值, 因为livedata.getvalue的值永远为null。
因此,viewModel的整体代码如下:
class StoryListViewModel(val app: Application): AndroidViewModel(app) {
companion object {
val TAG = "StoryListViewModel"
}
var storyList = MediatorLiveData<List<Story>>()
@Inject
lateinit var storyRes: StoryRepository
init {
(app as ZhiJokeApplication).component.inject(this)
}
fun getStoryList():LiveData<List<Story>> {
storyList.addSource(storyRes.loadAllStories(), {
if (it == null || it.size == 0) {
storyRes.simlutateInsertData()
Thread.sleep(1000)
}
storyList.postValue(it)
})
return storyList
}
}
getStoryList提供给UI,对storyList进行观察,根据变化去更新UI。
var viewModel = ViewModelProviders.of(activity).get(StoryListViewModel::class.java)
viewModel.getStoryList().observe(this, Observer {
if (it != null) {
// update UI
mAdapter.setStories(it)
}
})
到此为止,改架构大部分都介绍完了,感觉也没介绍的多详细可以自己实现一个demo感受一下。
几点建议:
使用dagger2来解耦。
如果rxjava2使用的非常熟练的话,也可以不用livedata。
这两篇文章也是对自己学习的一个回顾,其中穿插有自己对某些点的感想。
希望得到各位的建议。(wx:youquwen1226,欢迎交流)
后面打算写篇文章介绍dagger2.
若是不习惯用kotlin,我会再上传一个java版本的demo。
。
以前也有写过爬虫,抓过网易云歌单和豆瓣读书的数据,当时有两个问题解决的不够好, 自动化和登录。最近花时间用scrapy去写,自认为更好的解决了上述问题。这篇文章当作一个记录,也可当作学习教程(需要BeautifulSoup, selenium基本知识)。
用scrapy去抓取自从有了知乎,再也不用找福利了……收藏夹下每一个答案下的全部图片。
{'url': '/question/36007260', 'title': '女生坚持健身是种什么样的体验?', 'dec': ['健身']}
{'url': '/question/22132862', 'title': '女生如何选购适合自己的泳装?', 'dec': ['泳装']}
{'url': '/question/22918070', 'title': '女生如何健身锻造好身材?', 'dec': ['健身']}
{'url': '/question/24214727', 'title': '大胸妹子如何挑选合身又好看的比基尼?', 'dec': [ '比基尼']}
{'url': '/question/263451180', 'title': '你觉得健身时哪个训练动作最酷炫?', 'dec': ['健身']}
{'url': '/question/28586345', 'title': '有马甲线是种怎样的体验?', 'dec': ['马甲线']}
{'url': '/question/68734869', 'title': '2017 年,你解锁了哪些运动技能?可以用「视频」展示么?', 'dec': ['解锁']}
{'url': '/question/31983868', 'title': '有什么好看的泳装推荐?', 'dec': ['泳装']}
如上,对每一个问题提取url, 标题和关键字,保存到json文件方便后续工作。
一. 先从2开始,目标:如何拍好私房照?链接下的所有图片。
scrapy start zhihu
zhihu/spiders
:爬虫的主要文件。zhihu/items.py
:需要抓取的数据结构zhihu/middlewares.py
:中间键文件,selenium处理动态网页。zhihu/pipelines.py
:保存items中定义的数据结构或者下载图片(处理item)。其余文件都是额外生成,不属于爬虫目录。
cookies.pkl
:保存登录的cookies, 下次登录。
questions.json
: 保存所有问题的链接,方便后续使用。
上面两个文件都是在第一步用到, 后续再讲。
zhihu/spiders
下新建zhihu.py文件,从这里开始。import scrapy
class Zhihu(scrapy.Spider):
name = "zhihu"
urls = ["https://www.zhihu.com/question/22856657"]
yield request
def start_requests(self):
for url in self.urls:
request = scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
print(response.url)
print(response.body)
name
定义了爬虫的名字,urls
定义需要爬取的链接,从start_requests
开始,yield
对每一个url
执行得到生成器, scrapy
经过网络请求返回后调用parse
函数。
接下来在项目目录执行scrapy crawl zhihu
启动爬虫,看输出结果。
可以看到输出的url和html代码,最简单的爬虫执行完毕。
关键:该开始运行一定要日志输出。
遇到上述问题,需要打开settings
文件做如下设置:
#重试设置
RETRY_ENABLE = False
# 日志输出
LOG_ENABLED = True
LOG_LEVEL = "INFO"
取消失败重试,设置日志级别和是否输出(对爬取无影响)。
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
}
下载中间键中取消默认的UserAgent设置,以。及对robos.txt的设置。
BeautifulSoup
的使用可以看官方文档,简单明了。import scrapy
from bs4 import BeautifulSoup
class Zhihu(scrapy.Spider):
name = "zhihu"
urls = ["https://www.zhihu.com/question/22856657"]
def start_requests(self):
for url in self.urls:
request = scrapy.Request(url=url, callback=self.parse)
yield request
def parse(self, response):
print(response.url)
resSoup = BeautifulSoup(response.body, 'lxml')
items = resSoup.select("figure")
print(len(items))
for item in items:
print(item)
print(item.img)
print(item.img['data-original'])
parse
函数中,使用BeautifulSoup对网页分析。
结果如下:
对比输出,共计找到30个figure
标签。
分别对figure
,figure
的子标签img
及其data-original
属性进行输出。
粘贴属性到浏览器打开即可看到图片。
到此为止, 对如何拍好私房照?链接第一页的图片基本抓取完成。后面介绍怎么使用selenium对该链接下所有图片进行抓取。
有疑问请加weixin:youquwen1226,一起探讨。
github:https://github.com/yunshuipiao
上一篇文章实现的最简单的爬虫,抓取了某个链接下第一次加载的所有图片链接。因为存在下拉刷新, 因此怎么获得该页面的全部答案是这篇文章需要去处理的事情。
这里选择使用方案2,方案1后面遇到再讨论。
一:selenium的简单使用。
这里涉及selenium的安装,Selenium with Python官方文档讲解的特别简单。我使用的的chrome(可以配置无头属性)。
注意:需要将下载的driver配置环境变量,以便可以访问。
if __name__ == '__main__':
options = webdriver.ChromeOptions()
options.add_argument('headless')
# driver = webdriver.Chrome(options=options)
driver = webdriver.Chrome()
driver.implicitly_wait(2)
driver.get("https://www.zhihu.com/question/22856657")
time.sleep(2)
resSoup = BeautifulSoup(driver.page_source, 'lxml')
items = resSoup.select("figure > span > div")
print(len(items))
for item in items:
print(item)
#driver.close()
在项目执行代码可以看到输出:python zhihu/spiders/zhihu.py
可以看到共抓到198张图片,对去data-src
属性即可得图片链接。
代码解释:
前三行用于启动一个无头的driver。如果需要查看加载的情况,只用第四行代码即可,执行完毕可以查看浏览器打开的url, 如下。
接下来三行:
第一行启动driver的隐式等待,简单意思就是:2秒内网页加载完毕就往下执行,否则就加载完2秒,继续往下执行。
第二行用于打开链接,相当于手动在地址栏输入链接。
第三行延时,等待网页加载。
后面的内容前面有接触。
如果输出结果和我的相差不大,那么继续下一步。
二:selenium执行js代码,加载全部内容。
if __name__ == '__main__':
options = webdriver.ChromeOptions()
options.add_argument('headless')
# driver = webdriver.Chrome(options=options)
driver = webdriver.Chrome()
driver.implicitly_wait(2)
driver.get("https://www.zhihu.com/question/22856657")
time.sleep(2)
count = 1
css_selector = "#root > div > main > div > div.Question-main > div.Question-mainColumn > div > div.Card > button"
css_selector2 = "#root > div > main > div > div.Question-main > div.Question-mainColumn > div > div.CollapsedAnswers-bar"
while len(driver.find_elements_by_css_selector(css_selector)) == 0 and \
len(driver.find_elements_by_css_selector(css_selector2)) == 0:
print("count:" + str(count))
js = "var q=document.documentElement.scrollTop=" + str(count * 200000)
count += 1
driver.execute_script(js)
time.sleep(0.5)
resSoup = BeautifulSoup(driver.page_source, 'lxml')
items = resSoup.select("figure > span > div")
print(len(items))
for item in items:
print(item)
结果输出:共计抓取翻页13次,抓取662个图片链接。
中间部分新增的代码, count用于记录翻页次数。
css_selector和css_selector2用于判断某个元素是否存在,决定是否滑动到底部,如下。
用一个循环去执行js代码,简单意思是滑动到距离页面顶部x的距离。经过测试,200000/页是比较好的选择。
至此,可以抓取某个链接下的所有图片。
三: selenium与spider middlewares的结合。
上面一切顺利之后, 接下来去使双方结合。
关于scrapy 的下载中间键(DOWNLOADER_MIDDLEWARES):
简单来说,该中间键就是调用process_request, 将获取url的request经过处理,返回request,response,None三值之一。
返回 request:继续执行后面的process_request方法(包括中间键)
response:不知行后面的process_request方法,以此response结果直接返回,执行zhihu/spiders/zhihu.py 的回调方法。
具体请看官方文档: https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
(还有spider middlewares, 本次未用到)
话不多说,开始写代码:
在middlewares.py中定义自己的中间键:
class PhantomJSMiddleware(object):
def __init__(self):
options = webdriver.ChromeOptions()
options.add_argument('headless')
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(1)
def process_request(self, request, spider):
print(request.url)
driver = self.nextPage(request)
return HtmlResponse(url=request.url, body=driver.page_source, encoding="utf-8")
# 翻页操作
def nextPage(self, request):
self.driver.get(request.url)
time.sleep(2)
count = 1
css_selector = "#root > div > main > div > div.Question-main > div.Question-mainColumn > div > div.Card > button"
css_selector2 = "#root > div > main > div > div.Question-main > div.Question-mainColumn > div > div.CollapsedAnswers-bar"
# css_selector = "div > a > img"
# print(len(self.driver.find_elements_by_css_selector(css_selector)))
while len(self.driver.find_elements_by_css_selector(css_selector)) == 0 and len(
self.driver.find_elements_by_css_selector(css_selector2)) == 0:
print("count:" + str(count))
js = "var q=document.documentElement.scrollTop=" + str(count * 200000)
count += 1
self.driver.execute_script(js)
time.sleep(0.5)
print(count)
time.sleep(2)
return self.driver
@classmethod
# 信号的使用
def from_crawler(cls, crawler):
print("from_crawler")
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_closed)
return s
def spider_opened(self, spider):
print("spider close")
self.driver.close()
稍作解释:
__init__
函数做初始化工作,
nextPage
函数根据得到的request做翻页操作。
**process_request
**函数是中间键的必要函数, 启动中间键之后,yield生成器中的request都会经过该函数,然后返回结果(一定要在此函数执行return)。
后面是spiders信号的使用实例, 用于在spiders执行结束的时候做处理工作,比如关闭driver等操作。
setttings.py:配置对下载中间键的使用。
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'zhihu.middlewares.PhantomJSMiddleware': 100,
}
以上配置完毕,即可执行爬虫。
命令行执行 scrapy crawl zhihu
启动爬虫,注意看日志,有如下输出。
下拉刷新根据网速决定,所以count值会有不同。
可以看到这里抓取到了660张图片链接(允许个别误差)。
至此,对于使用scrapy结合selnium抓取动态网页已经不是问题。
对于某些需要登录的链接,打开url之后会直接去到登录页。下一篇文章介绍怎么使用selenium 去登录,保存cookies, 带着cookies去请求(可能是万能的登录方法,对于图片验证, 手机验证码也可能适用)。
微信:youquwen1226
github:https://github.com/yunshuipiao
欢迎来信一起探讨。
以后会在上面记录一下个人的学习心得,努力
add and delete
标签(空格分隔):Android
对于是否有开悬浮窗,程序是可以检测到的。
权限声明:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
对于android6.0 以上的机型来说,google将悬浮窗权限和其他危险权限单独列出,因此可以检测, 方法:
fun checkFloatWindowPermission(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val result = Settings.canDrawOverlays(BaseContext.application)
return result
} else {
//6.0 以下
val result = checkRomFloatWindowPermission()
return result
}
return true
}
android 4.4以下大部分机型声明即获取(包括大部分国产机), 因此只需判断4.4.4 -- 5.1之间的系统即可。
//如下,经过我的测试,只有小米和魅族, 华为需要在`api<=19`时单独处理,其他正常。
fun checkRomFloatWindowPermission(): Boolean {
val brand = Build.MANUFACTURER
if (brand.contains("Xiaomi")) {
//check miui permission
return checkMeizuAndMIUIFloatWindowPermission()
}
if (brand.contains("Huawei") || brand.contains("HUAWEI")) {
return checkMeizuAndMIUIFloatWindowPermission()
}
if (Build.VERSION.SDK_INT <= 19) {
return true
}
when (brand) {
"Meizu" -> return checkMeizuAndMIUIFloatWindowPermission()
"OPPO" -> return true
"vivo" -> return true
"Sony" -> return true
"Letv" -> return true
"LG" -> return true
}
return true
}
//-------------- Meizu MIUI huawei start---------------------
fun checkMeizuAndMIUIFloatWindowPermission(): Boolean {
if (Build.VERSION.SDK_INT >= 19) {
return checkMeizuAndMIUIOp(24)
}
return true
}
fun checkMeizuAndMIUIOp(op: Int): Boolean {
if (Build.VERSION.SDK_INT >= 19) {
val opsManager = BaseContext.application.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
try {
val clazz = AppOpsManager::class.java
val method = clazz.getDeclaredMethod("checkOp",
Int::class.javaPrimitiveType, Int::class.javaPrimitiveType, String::class.java)
return AppOpsManager.MODE_ALLOWED == method.invoke(opsManager, op, Binder.getCallingUid(), BaseContext.application.packageName)
} catch (e: Exception) {
Logger.d(TAG, "checkOp", e)
}
} else {
Logger.d(TAG, "Below API 19 cannot invoke!")
}
return false
}
//-------------- Meizu MIUI end ---------------------
至此,权限检查完毕。
不能动态获取,只能跳转到这只页去手动开启。
fun applyFloatWindowPermission(context: Context) {
if (Build.VERSION.SDK_INT >= 23) {
applySystemPermission(context)
return
}
val brand = Build.MANUFACTURER
when (brand) {
"Huawei", "HUAWEI" -> applyHuaweiPermission(context)
"Xiaomi" -> applyMIUIPermSetting(context)
"Meizu" -> applyMeizuPermission(context)
"OPPO" -> openOppoPermSetting(context)
"vivo" -> openVivoPermSetting(context)
else -> applySystemPermission(context)
}
}
//-------------- Sysytem start ---------------------
fun applySystemPermission(activity: Activity) {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
intent.data = Uri.parse("package:" + activity.packageName)
activity.startActivityForResult(intent, 1)
}
//-------------- Sysytem end ---------------------
//-------------- Meizu start---------------------
fun applyMeizuPermission(activity: Activity) {
val intent = Intent("com.meizu.safe.security.SHOW_APPSEC")
intent.setClassName("com.meizu.safe", "com.meizu.safe.security.AppSecActivity")
intent.putExtra("packageName", activity.packageName)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
activity.startActivity(intent)
}
//-------------- Meizu end ---------------------
//-------------- MIUI start---------------------
fun applyMIUIPermSetting(activity: Activity) {
openMIUIPermSetting(activity)
}
fun openMIUIPermSetting(context: Context): Boolean {
val i = Intent("miui.intent.action.APP_PERM_EDITOR")
val componentName = ComponentName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity")
i.component = componentName
i.putExtra("extra_pkgname", context.packageName)
try {
context.startActivity(i)
} catch (e: Exception) {
Logger.e(TAG, "openMIUIPermSetting jump e:" + e.message)
openSystemSetting(context)
return false
}
return true
}
//-------------- MIUI start---------------------
//-------------- huawei start---------------------
fun applyHuaweiPermission(context: Context) {
try {
val intent = Intent()
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
var comp = ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity")//悬浮窗管理页面
intent.component = comp
val version = getEmuiVersion()
Logger.d(TAG, "applyHuaweiPermission:" + version)
if (version == 3.0) {
comp = ComponentName("com.huawei.systemmanager", "com.huawei.notificationmanager.ui.NotificationManagmentActivity")//悬浮窗管理页面
intent.component = comp
context.startActivity(intent)
return
}
context.startActivity(intent)
// if (version == 3.1) {
// //emui 3.1 的适配
// context.startActivity(intent)
// } else {
// //emui 3.0 的适配
// comp = ComponentName("com.huawei.systemmanager", "com.huawei.notificationmanager.ui.NotificationManagmentActivity")//悬浮窗管理页面
// intent.component = comp
// context.startActivity(intent)
// }
} catch (e: Exception) {
//抛出异常时提示信息
Toast.makeText(context, "进入设置页面失败,请手动设置", Toast.LENGTH_LONG).show()
Log.e(TAG, Log.getStackTraceString(e))
}
}
fun getEmuiVersion(): Double {
try {
val emuiVersion = getSystemProperty("ro.build.version.emui") ?: return 4.0
val version = emuiVersion.substring(emuiVersion.indexOf("_") + 1)
return java.lang.Double.parseDouble(version)
} catch (e: Exception) {
e.printStackTrace()
}
return 4.0
}
//-------------- huawei end---------------------
附: 各手机厂商跳转到设置页的方法:(context)
// open app setting according to rom ----------------
fun applyBrandStrategy(activity: Activity) {
val brand = Build.MANUFACTURER
when (brand) {
"Huawei", "HUAWEI" -> openHuaweiPermSetting(activity)
"Xiaomi" -> openMIUIPermSetting(activity)
"Meizu" -> openMeizuPermSetting(activity)
"OPPO" -> openOppoPermSetting(activity)
"vivo" -> openVivoPermSetting(activity)
"Sony" -> openSonyPermSetting(activity)
"Letv" -> openLetvPermSetting(activity)
"LG" -> openLGPermSetting(activity)
else -> openSystemSetting(activity)
}
}
fun openMIUIPermSetting(activity: Activity): Boolean {
val i = Intent("miui.intent.action.APP_PERM_EDITOR")
val componentName = ComponentName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity")
i.component = componentName
i.putExtra("extra_pkgname", activity.packageName)
try {
activity.startActivity(i)
} catch (e: Exception) {
Logger.e(TAG, "openMIUIPermSetting jump e:" + e.message)
openSystemSetting(activity)
return false
}
return true
}
fun openMeizuPermSetting(activity: Activity): Boolean {
val intent = Intent("com.meizu.safe.security.SHOW_APPSEC")
intent.addCategory(Intent.CATEGORY_DEFAULT)
intent.putExtra("packageName", activity.packageName)
try {
activity.startActivity(intent)
} catch (e: Exception) {
Logger.e(TAG, "openMeizuPermSetting jump e:" + e.message)
openSystemSetting(activity)
return false
}
return true
}
fun openHuaweiPermSetting(activity: Activity): Boolean {
val intent = Intent()
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra("packageName", activity.packageName)
var comp = ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity")
intent.component = comp
try {
activity.startActivity(intent)
} catch (e: Exception) {
Logger.e(TAG, "openHuaweiPermSetting jump e:" + e.message)
comp = ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.SingleAppActivity")
intent.component = comp
try {
activity.startActivity(intent)
} catch (e1: Exception) {
Logger.e(TAG, "openHuaweiPermSetting jump e1:" + e.message)
openSystemSetting(activity)
return false
}
}
return true
}
fun openOppoPermSetting(activity: Activity): Boolean {
val intent = Intent()
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra("packageName", activity.packageName)
val comp = ComponentName("com.oppo.safe", "com.oppo.safe.permission.PermissionSettingsActivity")
intent.component = comp
try {
activity.startActivity(intent)
} catch (e: Exception) {
Logger.e(TAG, "openOppoPermSetting jump e:" + e.message)
openSystemSetting(activity)
return false
}
return true
}
fun openVivoPermSetting(activity: Activity): Boolean {
val intent = Intent()
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra("packageName", activity.packageName)
val comp = ComponentName("com.iqoo.secure", "com.iqoo.secure.MainActivity")
intent.component = comp
try {
activity.startActivity(intent)
} catch (e: Exception) {
Logger.e(TAG, "openVivoPermSetting jump e:" + e.message)
openSystemSetting(activity)
return false
}
return true
}
fun openSonyPermSetting(activity: Activity): Boolean {
val intent = Intent()
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra("packageName", activity.packageName)
val comp = ComponentName("com.sonymobile.cta", "com.sonymobile.cta.SomcCTAMainActivity")
intent.component = comp
try {
activity.startActivity(intent)
} catch (e: Exception) {
Logger.e(TAG, "openSonyPermSetting jump e:" + e.message)
openSystemSetting(activity)
return false
}
return true
}
fun openLGPermSetting(activity: Activity): Boolean {
val intent = Intent("android.intent.action.MAIN")
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra("packageName", activity.packageName)
val comp = ComponentName("com.android.settings", "com.android.settings.Settings\$AccessLockSummaryActivity")
intent.component = comp
try {
activity.startActivity(intent)
} catch (e: Exception) {
Logger.e(TAG, "openLGPermSetting jump e:" + e.message)
openSystemSetting(activity)
return false
}
return true
}
fun openLetvPermSetting(activity: Activity): Boolean {
val intent = Intent()
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.putExtra("packageName", activity.packageName)
val comp = ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.PermissionAndApps")
intent.component = comp
try {
activity.startActivity(intent)
} catch (e: Exception) {
Logger.e(TAG, "openLetvPermSetting jump e:" + e.message)
openSystemSetting(activity)
return false
}
return true
}
fun openSystemSetting(activity: Activity): Boolean {
val localIntent = Intent()
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
localIntent.action = "android.settings.APPLICATION_DETAILS_SETTINGS"
localIntent.data = Uri.fromParts("package", activity.packageName, null)
try {
activity.startActivity(localIntent)
} catch (e: Exception) {
Logger.e(TAG, "openSystemSetting jump e:" + e.message)
return false
}
return true
}
自启动比较简单,android系统无法检测到是否开启,一般做法是首次安装提示用户跳转设置:
private fun applySelfStartSetting() {
var intent = Intent()
try {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
var componentName: ComponentName? = null
val brand = Build.MANUFACTURER
when (brand) {
"Xiaomi" -> {
componentName = ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")
}
"samsung", "Samsung" -> {
componentName = ComponentName("com.samsung.android.sm" ,"com.samsung.android.sm.app.dashboard.SmartManagerDashBoardActivity")
}
"Huawei", "HUAWEI" -> {
componentName = ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity")
}
"Meizu" -> {
componentName = ComponentName.unflattenFromString("com.meizu.safe/.permission.SmartBGActivity")
}
"OPPO" -> {
componentName = ComponentName.unflattenFromString("com.oppo.safe/.permission.startup.StartupAppListActivity")
}
"vivo" -> {
componentName = ComponentName.unflattenFromString("com.iqoo.secure/.safeguard.PurviewTabActivity")
}
"Letv" -> {
intent.action = "com.letv.android.permissionautoboot"
}
else -> {
intent.action = "android.settings.APPLICATION_DETAILS_SETTINGS"
intent.data = Uri.fromParts("package", packageName, null)
}
}
intent.component = componentName
startActivity(intent)
} catch (e: Exception) {
Logger.d(PhoneDialogActivity.TAG, "applySelfStartSetting:" + e)
intent = Intent(Settings.ACTION_SETTINGS)
startActivity(intent)
}
}
所属包: android.os.Build
作用(含义): 从系统属性中提取设备硬件和版本信息
静态属性:
1. BOARD 主板:The name of the underlying board, like goldfish.
2. BOOTLOADER 系统启动程序版本号:The system bootloader version number.
3. BRAND 系统定制商:The consumer-visible brand with which the product/hardware will be associated, if any.
4. CPU_ABI cpu指令集:The name of the instruction set (CPU type + ABI convention) of native code.
5. CPU_ABI2 cpu指令集2:The name of the second instruction set (CPU type + ABI convention) of native code.
6. DEVICE 设备参数:The name of the industrial design.
7. DISPLAY 显示屏参数:A build ID string meant for displaying to the user
8. FINGERPRINT 唯一识别码:A string that uniquely identifies this build. Do not attempt to parse this value.
9. HARDWARE 硬件名称:The name of the hardware (from the kernel command line or /proc).
10. HOST
11. ID 修订版本列表:Either a changelist number, or a label like M4-rc20.
12. MANUFACTURER 硬件制造商:The manufacturer of the product/hardware.(我们目前只需要关注这个静态属性即可)
13. MODEL 版本即最终用户可见的名称:The end-user-visible name for the end product.
14. PRODUCT 整个产品的名称:The name of the overall product.
15. RADIO 无线电固件版本:The radio firmware version number. 在API14后已过时。使用 getRadioVersion()代替。
16. SERIAL 硬件序列号:A hardware serial number, if available. Alphanumeric only, case-insensitive.
17. TAGS 描述build的标签,如未签名,debug等等。:Comma-separated tags describing the build, like unsigned,debug.
18. TIME
19. TYPE build的类型:The type of build, like user or eng.
20. USER
所属包: android.content.ComponentName
构造方法使用方式如下:
adb为我们提供了一个可以打印出当前系统所有service信息,在后面可加上具体的服务名的命令
adb shell dumpsys
获取设备电池信息:adb shell dumpsys battery
获取cpu信息:adb shell dumpsys cpuinfo
获取内存信息:adb shell dumpsys meminfo
要获取具体应用的内存信息,可加上包名
adb shell dumpsys meminfo PACKAGE_NAME
获取Activity信息:adb shell dumpsys activity
获取package信息:adb shell dumpsys package
加上-h可以获取帮助信息
获取某个包的信息:adb shell dumpsys package PACKAGE_NAME
获取通知信息:adb shell dumpsys notification
获取wifi信息:adb shell dumpsys wifi
可以获取到当前连接的wifi名、搜索到的wifi列表、wifi强度等
获取电源管理信息:adb shell dumpsys power
可以获取到是否处于锁屏状态:mWakefulness=Asleep或者mScreenOn=false
获取电话信息:adb shell dumpsys telephony.registry
可以获取到电话状态,例如mCallState值为0,表示待机状态、1表示来电未接听状态、2表示电话占线状态
mCallForwarding=false #是否启用呼叫转移
mDataConnectionState=2 #0:无数据连接 1:正在创建数据连接 2:已连接mDataConnectionPossible=true #是否有数据连接mDataConnectionApn= #APN名称等
adb shell dumpsys activity top
//获取当前显示activity的详细信息,包括所属包名和activity名,activity的布局信息等等。
参考资料 : http://www.voidcn.com/article/p-dpiicqfm-zw.html
Anroid6.0以下, 权限申明后即可获取(国产定制系统除外)
在manifest文件里声明权限:
//电话相关权限
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
已打电话权限为例说明(一般情况):
private void checkAndRquestCallPermission() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "checkCallPermission: " + "没有打电话权限");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 1);// 1:code
}
}
//重载activity的onRequestPermissionsResult方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 1) {
if (1 == grantResults.length && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
Log.d(TAG, "onRequestPermissionsResult: " + "已获取:" + permissions[0] + " 权限");
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
对shouldShowRequestPermissionRationale方法的补充说明:
在请求权限时,这个函数用来可以用来给用户提示为什么需要权限(用户没有勾选不再提示按钮,第一次请求或是拒绝后返回ture):
private void checkAndRquestCallPermission() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "checkCallPermission: " + "没有电话权限");
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
Log.d(TAG, "checkCallPermission: " + "need phone permission");
//弹出对话框,提示用户允许或者拒绝,允许则请求权限
}
//此处默认允许
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, 1);// 1:code
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 1) {
if (1 == grantResults.length && PackageManager.PERMISSION_GRANTED == grantResults[0]) {
Log.d(TAG, "onRequestPermissionsResult: " + "以获取:" + permissions[0] + " 权限");
}
else {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
// //用户没有勾选不再提示,并拒绝
Log.d(TAG, "onRequestPermissionsResult: " + "refuse one time");
} else {
// 用户勾选提示并拒绝
Log.d(TAG, "onRequestPermissionsResult: " + "refuse forever");
//跳转权限设置页
}
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
根据测试,小米机型貌似没有可勾选功能,一次拒绝默认永远拒绝。下次申请到权限设置页。
(国产机型区分对待)
具体代码参考
https://github.com/yunshuipiao/SWBase/blob/Sbranch/app/src/main/java/com/macmini/swensun/swbase/MainActivity.java
简单介绍此功能的实现
在有电话权限的前提下
在有电话权限的前提下
功能比较简单,看代码就行:
继承BroadcastReceiver类处理即可:
public class PhoneReceiver extends BroadcastReceiver {
private static final String TAG = "PhoneReceiver";
private boolean mInComingFlag = false;
private static String ACTION_NEW_INCOMMING_CALL = "android.intent.action.PHONE_STATE";
private PhoneStateListener listen = new PhoneStateListener(){
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
//电话打进响铃中,电话打出没有此状态
mInComingFlag = true;
Log.d(TAG, "onCallStateChanged: " + incomingNumber);
break;
case TelephonyManager.CALL_STATE_IDLE:
//挂断电话
if (mInComingFlag) {
Log.d(TAG, "onCallStateChanged: " + "call hang up");
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
//电话接听
if (mInComingFlag) {
Log.d(TAG, "onCallStateChanged: " + "接听电话:" + incomingNumber);
}
}
}
};
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
//电话打出
mInComingFlag = false;
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Log.d(TAG, "onReceive: " + "call phoneNumber:" + phoneNumber);
}
if (intent.getAction().equals(ACTION_NEW_INCOMMING_CALL)) {
//电话打进
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
tm.listen(listen, PhoneStateListener.LISTEN_CALL_STATE);
}
}
}
接着在manifest声明该receiver和需要过滤的广播
<receiver android:name=".phone.PhoneReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
<action android:name="android.intent.action.PHONE_STATE"/>
<action android:name="android.intent.action.MY_SELF_RECEIVER"/>
</intent-filter>
</receiver>
自定义广播发送并接收(同上)
//发送自定义广播,PhoneReceiver接收
Intent intent = new Intent(ACTION);
intent.putExtra("Msg", "helloReceiver");
sendBroadcast(intent);
//选择语言并保存状态
private void changeLanguage() {
//弹出对话框或者其他方式选择语言,并持久化保存到本地
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setSingleChoiceItems(new String[]{"auto", "English", "简体中文"},
getSharedPreferences("language", Context.MODE_PRIVATE).getInt("language", 0),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences preferences = getSharedPreferences("language", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
Log.d(TAG, "onClick: " + which);
//保存设置
editor.putInt("language", which);
editor.apply();
dialog.dismiss();
Intent intent = new Intent(MultiLanguageActivity.this, MultiLanguageActivity.class);
//重新打开一个返回栈并清除前者
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
//加载所保存的语言
private void setLanguage() {
SharedPreferences preferences = getSharedPreferences("language", Context.MODE_PRIVATE);
int language = preferences.getInt("language", 0);
Resources resources = getResources();
DisplayMetrics dispalyMetRics = resources.getDisplayMetrics();
Configuration configuration = resources.getConfiguration();
switch (language) {
case 0:
configuration.setLocale(Locale.getDefault());
break;
case 1:
configuration.setLocale(Locale.ENGLISH);
break;
case 2:
configuration.setLocale(Locale.CHINESE);
break;
}
// FIXME: 2017/6/5
resources.updateConfiguration(configuration, dispalyMetRics);
}
因为对ActionBar不起作用,因此需要调用以下方法设置title显示。
getSupportActionBar().setTitle(R.string.app_name);
判断当前sim使用情况:
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private int checkDualSim() {
int simNumber = 0;
SubscriptionManager sm = SubscriptionManager.from(this);
List<SubscriptionInfo> subs = sm.getActiveSubscriptionInfoList();
if (subs == null) {
d(TAG, "checkDualSim: " + "no sim");
return simNumber;
}
if (subs.size() > 1) {
simNumber = 2;
d(TAG, "checkDualSim: " + "two sims");
} else {
d(TAG, "checkDualSim: " + "one sim");
simNumber = 1;
}
for (SubscriptionInfo s: subs) {
d(TAG, "checkDualSim: " + "simInfo:" + subs.toString());
}
return simNumber;
}
//根据上述情况,初始化UI和拨打电话,尤其注意sim2的打电话情况
@RequiresApi(api = Build.VERSION_CODES.M)
private void callPhone(boolean isDualSim) {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "callPhone: " + "no call phone permission");
return;
}
String phoneNumber = mBinding.etPhoneNumber.getText().toString().trim();
phoneNumber = TextUtils.isEmpty(phoneNumber) ? "13422284669" : phoneNumber;
if (!isDualSim) {
//单卡
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:" + phoneNumber));
startActivity(intent);
return;
}
TelecomManager telecomManager = (TelecomManager)getSystemService(Context.TELECOM_SERVICE);
if(telecomManager != null) {
List<PhoneAccountHandle> phoneAccountHandleList = telecomManager.getCallCapablePhoneAccounts();
d(TAG, "callPhone: " + phoneAccountHandleList);
d(TAG, "callPhone: " + phoneAccountHandleList.get(1).toString());
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:" + phoneNumber));
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandleList.get(1));
startActivity(intent);
}
}
以上情况实现比较粗略,涉及工业代码和机型适配还需细细斟酌。
为什么这里仅仅是后序遍历?因此后序遍历稍微要麻烦一点,默认对其他五种遍历已经了解(递归和非递归的前序, 中序遍历,还有层次遍历。
这里有两种情况,
def post_order_traversal_tree(root):
if root is None:
return
post_order_traversal_tree(root.left_child)
post_order_traversal_tree(root.right_child)
print(root.data, end=" ")
下面着重分析一下后序的非递归遍历
思路:对于任一节点将其入栈。若左右节点都已访问过,则访问该节点并出栈。否则将右子树入栈,左子树入栈, 保证左子树在右子树之前访问。
def post_order_traversal_tree_two(root):
if root is None:
return
node_list = []
node_list.append(root)
node_set = set()
node_set.add(None)
while len(node_list) != 0:
temp_node = node_list[-1]
if temp_node.left_child in node_set and temp_node.right_child in node_set:
print(temp_node.data, end=" ")
node_list.pop()
node_set.add(temp_node)
else:
if temp_node.right_child is not None:
node_list.append(temp_node.right_child)
if temp_node.left_child is not None:
node_list.append(temp_node.left_child)
print()
思路;对于任一节点将其入栈。然后一直搜索左子树直到空。此时对于栈顶节点,由于还要访问右子树,所以不能出栈。对于该节点的右子树做相同的操作。当右子树访问并出栈后,该节点又出现在栈顶,此时出栈并访问。因此对于每一个节点,都在栈顶出现两次,用一个标记或者集合来判断该节点是第几次出现,做对应的操作。
def post_order_traversal_tree_three(root):
if root is None:
return
node_list = []
node_set = set()
node_set.add(None)
temp_node = root
while len(node_list) != 0 or temp_node is not None:
while temp_node is not None:
node_list.append(temp_node)
temp_node = temp_node.left_child
if len(node_list) != 0:
temp_node = node_list[-1]
if temp_node not in node_set : #第一次出现在栈顶,对右子树做相同操作。
node_set.add(temp_node)
temp_node = temp_node.right_child
else:
print(temp_node.data, end=" ")
node_list.pop()
temp_node = None #最后注意temp_node 出栈后,置为空,防止再次入栈。
print()
这一种方式比较讨巧,后序是 左 -- 右 -- 中。
而前序遍历是 中--左--右 ,那么如果能 中--右--左 遍历,倒转就得到了后序遍历的结果,其实也是一种右在先的前序遍历,代码如下:
#根节点入栈。根节点出栈并打印。左子树入栈,右子树入栈。重复上述操作。将打印结果反转即是后序遍历的结果。
def post_order_traversal_tree_four(root):
if root is None:
return
node_list = []
result = []
node_list.append(root)
while len(node_list) != 0:
temp_node = node_list.pop()
result.append(temp_node.data)
if temp_node.left_child is not None:
node_list.append(temp_node.left_child)
if temp_node.right_child is not None:
node_list.append(temp_node.right_child)
for i in reversed(result):
print(i, end=" ")
print()
以上就是我理解的对于二叉树后续遍历的四种方法。
对于其余五种遍历的实现,请查看下面地址,都有简单注释。
github: https://github.com/yunshuipiao/sw-algorithms/tree/master/python
关于这个项目:最基本的算法是每一个计算机从业者必须深刻理解的内容。
因此新建这个项目来学习并用不同语言来实现最基本的算法。
[基本算法:https://github.com/yunshuipiao/sw-algorithms/blob/master/questions.md]
目前语言有python, kotlin, java, c++。
最长递增子序列: 动态规划和LCS(最长公共子序列)
子序列和子串的区别:子序列不连续,字串连续。
这个题两种解法
下面分别做简单介绍:
O(n^2)时间复杂度。想求的array[0, i]的最大递增子序列。则计算array[0, i- 1]中以各元素为最后元素的最长递增序列。与array[i]比较, 因为不连续。
def longest_increasing_subsequence_one(array):
temp_array = [1] * len(array)
for index, _ in enumerate(array):
for i in range(0, index):
if array[i] < array[index] and temp_array[i] + 1 >= temp_array[index]:
temp_array[index] = temp_array[i] + 1
return max(temp_array)
也是动态规划。
若两序列A[i]的元素和B[i]的元素相等,那么最长公共子序列为A[0, i -1], B[0, j - 1]的最长公共子序列的值加1。否则分别是A[0, i- 1], B[0 ,j] 或者A [0, i - 1], B[0, j ]的最长公共子序列的较大值。
其中选择一个二维数组来标记记住A[i], B[j]的最长公共子序列,见代码。
#复制列表并排序,求两列表的最长公共子序列( LCS ): 动态规划
def longest_increasing_subsequence_two(array):
copy_array = array[:]
copy_array.sort()
#中间结果
temp_array = [[0 for i in range(len(array))] for j in range(len(copy_array))]
for i in range(1, len(array)):
for j in range(1, len(copy_array)):
if array[i] == copy_array[j]:
temp_array[i][j] = temp_array[i - 1][j - 1] + 1
else:
temp_array[i][j] = max(temp_array[i][j - 1], temp_array[i - 1][j])
#倒推求出最长公共子序列
result = []
i = len(array) - 1
j = len(copy_array) - 1
while i > 0 and j > 0:
if array[i] == copy_array[j]:
result.append(array[i])
i -= 1
j -= 1
else:
if temp_array[i][j - 1] >= temp_array[i - 1][j]:
j -= 1
else:
i -= 1
result.reverse()
print(result)
print(len(result))
标签(空格分隔): 知乎
最近两天一直在被 Rxjava2 的一些用法困扰,比如怎么去写一个 observable 的 listener, 怎么去体现响应式(观察者模式)。网上找了大部分资料,都只涉及怎么简单用订阅,链式发送字符串。
最终在 stackoverflow 找到了灵感,特此记录:
这是一个例子,对大部分监听都适用。使用场景如下:
比如想在 button 点击后异步去获得数据,成功后再主线程更新 UI。实现如下:
//注意listener的实现
Observable.create(new ObservableOnSubscribe<View>() {
@Override
public void subscribe(final ObservableEmitter<View> e) throws Exception {
listener = new View.OnClickListener() {
@Override
public void onClick(View v) {
//do something
if (!e.isDisposed()) {
e.onNext(v);
}
}
};
}
}).subscribe(new Consumer<View>() {
@Override
public void accept(@NonNull View view) throws Exception {
((Button)view).setText(i++ + "---");
}
});
//button监听没有区别
button.setOnClickListener(listener);
这只是一个简单的实现例子,达到同样的效果不难。作为例子,可以实现这样的接口:
private interface OnGetDataListener {
void success(String data);
void errorResult(Error error);
}
/配合Rxjava2的observer的onNext, onComplete, onError, onSubscribe等去接收,错误时取消订阅等功能。
想在list.add()操作是获得响应,并打印列表。(这只是一个例子)
//首先申明list和一个subject观察者并初始化
private List<Integer> list = new ArrayList<>(0);
private Subject<List<Integer>> subject;
//oncreate()订阅
subject = PublishSubject.create();
subject.subscribe(new Consumer<List<Integer>>() {
@Override
public void accept(@NonNull List<Integer> integers) throws Exception {
Log.d(TAG, "accept: " + integers);
}
});
//需要的时候发送:list加数据
private void addInteger(int i) {
list.add(i++);
subject.onNext(list);
}
这是大概流程,可扩展其他接收结果,取消订阅等。
完整代码:https://github.com/yunshuipiao/SWBlog/blob/master/Code/Rxjava.java
今天上午在使用属性动画时,一直在研究重复restart结束的闪动效果。
解决办法:添加线性动画插值器LinearInterpolator即可
默认AccelerateDecelerateInterpolator(先加后减)
在当前模块的buide.gradle文件加入:
tasks.withType(org.gradle.api.tasks.compile.JavaCompile) {
options.encoding = "UTF-8"
}
Application mApp = (Application) getApplication();
Log.e(TAG, "getApp: "+mApp);
Context mContext = getApplicationContext();
Log.e(TAG, "onAppContext: "+mContext);
Context mContext2 = mApp.getApplicationContext();
Log.e(TAG, "onAppContext: "+ mContext2);
Log.e(TAG, "onBaseContext: "+ getBaseContext());
Log.e(TAG, "onThis: "+MainActivity.this);
TextView mTextView = (TextView) findViewById(R.id.textview);
Log.e(TAG, "ongetContentDescription(): "+mTextView.getContentDescription());
Log.e(TAG, "onGetContext: " + mTextView.getContext());
//输出结果
com.example.sw.testproject E/MainActivity: getApp: android.app.Application@fd09430
com.example.sw.testproject E/MainActivity: onAppContext: android.app.Application@fd09430
com.example.sw.testproject E/MainActivity: onAppContext: android.app.Application@fd09430
com.example.sw.testproject E/MainActivity: onBaseContext: android.app.ContextImpl@615e8a9
com.example.sw.testproject E/MainActivity: onThis: com.example.sw.testproject.MainActivity@279780b
com.example.sw.testproject E/MainActivity: ongetContentDescription(): null
com.example.sw.testproject E/MainActivity: onGetContext: com.example.sw.testproject.MainActivity@279780b
//说明:getContext()方式是对于一个activity中的view来说,供其调用,返回这个activity。
activity:展示组件, 接收输入信息和交互,可显示或者隐式Intent启动触发, 前台界面的角色。
Service:计算性组件。有启动和绑定状态,运行在主线程中。
BroadcastRecevier:消息型组件。也叫广播,静态注册和动态注册。
ContentProvider:数据共享型组件。
bundle和序列化parcelable。
###判断网络连接状态
private void checkNetworkConnection() {
// BEGIN_INCLUDE(connect)
ConnectivityManager connMgr =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
if (activeInfo != null && activeInfo.isConnected()) {
wifiConnected = activeInfo.getType() == ConnectivityManager.TYPE_WIFI;
mobileConnected = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE;
if(wifiConnected) {
Log.i(TAG, getString(R.string.wifi_connection));
} else if (mobileConnected){
Log.i(TAG, getString(R.string.mobile_connection));
}
} else {
Log.i(TAG, getString(R.string.no_wifi_or_mobile));
}
// END_INCLUDE(connect)
}
String licenseTips = getString(R.string.app_license_tips);
SpannableString ss = new SpannableString(licenseTips);
ss.setSpan(new UnderlineSpan(), 7, licenseTips.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mLicenseTips.setText(ss);
###获取设备宽高
DisplayMetrics dm = getResources().getDisplayMetrics();
int w_screen = dm.widthPixels;
int h_screen = dm.heightPixels;
定时器,具体涉及到的工具类,Timer, TimerTask, Handle
现在全部可用Rxjava2代替。
setContentView()方法的内部也是用LayoutInflater来加载布局的,只不过这部分源码是internal的。
layoutInflater的实例之后就可以调用他的inflater方法来加载布局。
layoutInflater.inflate(resouceId, root)
inflate(int resource, ViewGroup parent, boolean attachToRoot)
: 根据参数名判断用法。
mContentView = LayoutInflater.from(getActivity().inflate(R.layout.fragment_home_contact, null)); //从指定xml加载一个view
需要重写onMeasure()测量自身宽度,重写onLayout(),算出每个子view的大小,联合屏幕高度进行计算。
//子view的类型和宽高可以自己设定。
public class AutoBreakViewGroup extends ViewGroup {
private int mScreenWidth;
private int mHorizontalSpacing;
private int mVerticalSpacing;
public AutoBreakViewGroup(Context context) {
super(context);
init();
}
public AutoBreakViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public AutoBreakViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
DisplayMetrics dm = getResources().getDisplayMetrics();
mScreenWidth = dm.widthPixels;
}
public void setmHorizontalSpacing(int mHorizontalSpacing) {
this.mHorizontalSpacing = mHorizontalSpacing;
}
public void setmVerticalSpacing(int mVerticalSpacing) {
this.mVerticalSpacing = mVerticalSpacing;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int mTotalHeight = 0;
int mTotalWidth = 0;
int mTempHeight = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
int measureHeight = childView.getMeasuredHeight();
int measureWidth = childView.getMeasuredWidth();
mTempHeight = (measureHeight > mTempHeight) ? measureHeight : mTempHeight;
if ((measureWidth + mTotalWidth + mHorizontalSpacing) > mScreenWidth) {
mTotalWidth = 0;
mTotalHeight += (mTempHeight + mVerticalSpacing);
mTempHeight = 0;
}
childView.layout(mTotalWidth + mHorizontalSpacing, mTotalHeight,
measureWidth + mTotalWidth + mHorizontalSpacing, mTotalHeight + measureHeight);
mTotalWidth += (measureWidth + mHorizontalSpacing);
}
}
}
可用google的flexlayout布局替换, 不必重写view。
前不久Google IO 2017不仅将kotlin宣布为官方开发语言,还发布了谷歌官方 Android 应用架构库,这个新的架构库旨在帮助我们设计健壮、可测试的和可维护的应用程序。新项目也打算采用这套架构,下面一步步介绍怎么去配置和使用这套架构。(最简单介绍,详细内容看官方文档和其他参考资料)
简单说来,该架构由数据驱动, 彻底将UI和data分离,UI层很轻,不涉及任何数据的操作的内容。ViewModel将数据的变化的反映在UI上,本身也不持有数据。官方推荐所有数据持久化。viewmodel通过Repository来管理数据,保存到数据库或者从网络获取。
本次打算使用kotlin来进行开发
新建一个项目,完成后在project/build.gradle添加以下内容
//对照添加
buildscript {
ext.kotlin_version = '1.1.2-5'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
在app/build.gradle的开头添加:
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
简单解释:
第二行表示kotlin扩展,可以避免使用类似findviewbyid的很多内容
第三行表示kotlin对注解的处理,感觉对databinding的支持不是很好。
到此为止, kotlin的配置已经完成。
在android studio的插件中安装kotlin后重启。
对Mainactivity.java执行code--convert java file to koltin file。
编译运行成功的话,说明kotlin配置成功
该架构官方翻译参考:https://juejin.im/post/5937b1d7a22b9d005810b877
简单配置如下
在project/build.gradle中添加:
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
}
}
在app/build.gradle中添加
/// Architecture Components
compile "android.arch.lifecycle:runtime:$ac_version"
compile "android.arch.lifecycle:extensions:$ac_version"
kapt "android.arch.lifecycle:compiler:$ac_version"
/// Room
compile "android.arch.persistence.room:runtime:$ac_version"
kapt "android.arch.persistence.room:compiler:$ac_version"
前面三行是关于lifecycle,livedata, viewmodel的依赖,后面是关于Room的依赖。
后面用例子进行说明。如有问题欢迎讨论。
Room在sqlite之上提供了一个抽象层。
将数据持久化到本地对于应用程序处理大量结构化数据有非常大的好处。最常见的情况是缓存相关数据。这样,当设备无法访问网络时,用户仍然可以在离线状态下浏览内容。然后,在设备重新上线后,任何用户发起的内容变更都会同步到服务器。
其中有三个组件组成:
下面介绍最简单的使用方法:
如上图所示,项目结构对应架构图,另外的base,App是一些基础的ui, 项目的app单例,以后还会添加dagger2来分离模块。
新建一个Story文件,内容如下:
@Entity(tableName = "stories")
class Story {
@PrimaryKey
var id = 0
var data = ""
var displayData = ""
var title = ""
var image = ""
}
//story作为数据库的一张表,可以指定列名,主键
新建StoryDao文件:
@Dao
interface StoryDao {
@Query("select * from stories")
fun loadAllStories(): LiveData<List<Story>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertStories(list: List<Story>)
}
//对数据的操作,这里涉及插入和查询(查询的返回值可以是LiveData,list, Rxjava2等)
新建AppDatabase文件:
@Database(entities = arrayOf(Story::class), version = 1)
abstract class AppDatabase: RoomDatabase() {
companion object {
val TAG = "sw_story_db"
}
abstract fun storyDao(): StoryDao
}
//数据库, 通过dao操作数据。
完成这三个文件,表示Room组件已经可以使用。
新建DataBaseManager文件:
object DatabaseManager {
lateinit var db: AppDatabase
fun initDb(context: Context) {
db = Room.databaseBuilder(context, AppDatabase::class.java, AppDatabase.TAG).build()
}
fun insertStories(storys: List<Story>) {
Flowable.just(storys)
.observeOn(Schedulers.io())
.subscribe {
db.beginTransaction()
try {
db.storyDao().insertStories(storys)
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}
}
fun loadAllStories(): LiveData<List<Story>> {
return db.storyDao().loadAllStories()
}
fun simlutateInsertData() {
var list = ArrayList<Story>()
for (i in 1..20) {
var s = Story()
s.id = (i)
s.data = ((i * i).toString() + "--" + i.toString())
s.displayData = ((i * i).toString() + "--" + i.toString())
s.title = ((i * i).toString() + "--" + i.toString())
s.image = ((i * i).toString() + "--" + i.toString())
list.add(s)
}
insertStories(list)
}
}
//该单例对数据库封装了一层,方便处理db的各种操作(db初始化可使用依赖注入)
数据库插入不能在主线程,涉及事务。
到此为止,该框架的数据库部分已经可以使用。
下面作为例子说明怎么存数据。
//继承Appication,初始化并模拟插入数据
class ZhiJokeApplication : Application() {
override fun onCreate() {
super.onCreate()
DatabaseManager.initDb(this)
DatabaseManager.simlutateInsertData()
}
}
编译项目运行,此时真机上应该保存有插入的数据。
####怎么查看
用一台已经root后的测试机,安装root文件管理器后,在根目录的 data/data/包名/database, 就能看到数据库新建并插入的数据了。
用DatabaseManage.loadAllStories()
即可取出数据。
未玩待续
完整代码:github
前面的文章介绍了怎么用scrapy去完成一个爬虫,涉及动态抓取和登录等操作。这篇文章简单介绍怎么让爬虫运行在服务器的docker里。
首先安装sshfs工具,可以参考网上教程。
接着切换到个人目录,新建 aliyun
文件夹。
localhost:~ swensun$ cd ~
localhost:~ swensun$ mkdir aliyun
localhost:~ swensun$ sshfs 120.78.202.210:/ aliyun/
之后输入密码,打开aliyun目录,即可看到的服务器上的目录挂载到了本地,可以本地编辑,保存。
/Users/swensun/aliyun/home/swensun/App/docker/spider
在该目录下新建Dockerfile文件,最简单内容如下,
FROM python:latest
RUN apt-get update \
&& apt-get install -y vim \
&& rm -rf /var/lib/apt/lists/*
这里执行了最简单的操作,下载python镜像,并执行RUN后面的命令。
ssh连接到服务器, 切到相应目录,
执行sudo docker build -t pythonspider .
命令, 下载生成镜像, 输出如下:
执行sudo docker images
命令,可以看到已经生成了pythonspider镜像。
docker-compose.yml
文件内容:
version: '2'
services:
spider:
image: pythonspider
volumes:
- ./zhihu:/spider
tty: true
简单解释:services可启动多个服务,比如数据库,nignx和执行程序配合生成一个容器。该服务叫spider,利用前面下载的pythonspider镜像,将./zhihu
数据卷同步到docker中。tty保证创建容器后保持后台运行,以免创建后关闭。
执行如下命令:
如上,可以看到创建的容器正在运行。
下面进入容器运行爬虫
可以看到数据卷spider已经在根目录。进入根目录,执行爬虫(需要安装python需要的包,也可以在前面的dockerfile中安装)。
可以看到和本地输出了一样的结果,说明docker中运行scrapy成功。
关于Dockerfile和docker-compose的其他命令及其高级用法,我也不是很懂,后面慢慢研究。
总结:
本文介绍了sshfs工具的使用,以及docker的安装与部署。
(Dockerfile和docker-compose)。
weixin:youquwen1226
github:https://github.com/yunshuipiao
欢迎来信一起探讨。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.