Java中的Threadlocal源代码学习

ThreadLocal是什么回过头,想想ThreadLocal实现了什么样的功能。举个例子,当不同的线程都去执行同样一个语句以获得当前线程的Looper时,要怎么实现?或许吧,ThreadLocal就实现了这样一个功能。在Looper中,申明了一个如下的静态变量,说明只有一个。static fina
阅读更多

Python语法简要概览

很久没用过Python了,熟悉一下用法准备ms。

输入和输出

1
2
3
4
>>> var=input('input:')
input:sjdf asdkjf 123 adsf ;dfa--..
>>> print(var)
sjdf asdkjf 123 adsf ;dfa--..

暂时理解input()读入一行数据,且可以加入提示信息。
读入一个整数:

1
2
s = input('birth: ')
birth = int(s)

基本注意事项

# 注释某行中其后的内容。
缩进代替C系列语言中的大括号。
大小写敏感。
字符串可用''或""包裹, \可用于转义。\n\t等
r’’表示’’内部的字符串默认不转义
'''表示多行输入

1
2
3
4
5
6
7
8
9
10
11
>>> print(r'\\\t\\')
\\\t\\
>>> print('''hi
... hi
... hi
... hello,py
... ''')
hi
hi
hi
hello,py

空值是一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。
用全部大写的变量名表示常量。
三种除法,//地板除,/整除,得到浮点数,%取余。
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符。
格式化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'
```
## 有意思的数据类型
#### list
```python
>>> fruits = ['apple', 'banana', 'orange']
>>> fruits
['apple', 'banana', 'orange']
>>> len(fruits)
3
>>> fruits[1]
'banana'
>>> fruits[6]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
  • 倒数
    1
    2
    3
    4
    5
    6
    7
    8
    >>> fruits[-1]
    'orange'
    >>> fruits[-2]
    'banana'
    >>> fruits[-6]
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    IndexError: list index out of range
    append()
    insert(1, ‘hi’)
    pop()
    list中的元素类型可以不同

tuple

不可更改的list,声明用()
当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来。
t = (1)定义的是自然数1,要定义成tuple需要加‘,’,规则。
t = (‘a’, ‘b’, [‘A’, ‘B’])其中的list是可变的。

dict

1
2
3
4
5
6
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95
>>> d['Adam'] = 67
>>> d['Adam']
67
  • 判断是否存在dict中的两个方法
    一是通过in判断key是否存在;
    二是通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value。
    1
    2
    3
    4
    5
    >>> 'Thomas' in d
    False
    >>> d.get('Thomas')
    >>> d.get('Thomas', -1)
    -1
    删除用pop(‘Bob’)。

set

1
2
3
s = set([1, 2, 3])
s.add(4)
s.remove(4)

可以对集合进行&和|操作。

判断&循环

if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
age = 3
if age >= 18:
print('adult')
elif age >= 6:
print('teenager')
else:
print('kid')

s = input('birth: ')
birth = int(s)
if birth < 2000:
print('00前')
else:
print('00后')

for…in

可打印list、tuple中的数据。

1
2
3
4
5
6
7
8
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)

sum = 0
for x in range(101):
sum = sum + x
print(sum)

while

1
2
3
4
5
6
sum = 0
n = 99
while n > 0:
sum = sum + n
n = n - 2
print(sum)

break&continue

同C系列语言

函数

内置函数

  • abs()
  • int()
  • max()
  • hex()
  • isinstance()
    函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:
    1
    2
    3
    >>> a = abs # 变量a指向abs函数
    >>> a(-1) # 所以也可以通过a调用abs函数
    1

自定义函数

1
2
3
4
5
def my_abs(x):
if x >= 0:
return x
else:
return -x
  • pass
    什么都不做,作为占位符
  • 返回多个值
    在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple

函数参数

  • 默认参数

    1
    2
    3
    4
    5
    6
    def power(x, n=2):
    s = 1
    while n > 0:
    n = n - 1
    s = s * x
    return s
  • 可变参数

    1
    2
    3
    4
    5
    6
    7
    8
    def calc(*numbers):
    sum = 0
    for n in numbers:
    sum = sum + n * n
    return sum
    >>> nums = [1, 2, 3]
    >>> calc(*nums)
    14

    *nums表示把nums这个list的所有元素作为可变参数传进去。

  • 关键字参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)
    >>> person('Michael', 30)
    name: Michael age: 30 other: {}
    >>> person('Bob', 35, city='Beijing')
    name: Bob age: 35 other: {'city': 'Beijing'}
    >>> person('Adam', 45, gender='M', job='Engineer')
    name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
    >>> extra = {'city': 'Beijing', 'job': 'Engineer'}
    >>> person('Jack', 24, city=extra['city'], job=extra['job'])
    name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
    >>> extra = {'city': 'Beijing', 'job': 'Engineer'}
    >>> person('Jack', 24, **extra)
    name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

    **extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra

  • 命名关键字参数

    • 命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。
    • 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了。
    • 命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      def person(name, age, *, city, job):
      print(name, age, city, job)
      >>> person('Jack', 24, city='Beijing', job='Engineer')
      Jack 24 Beijing Engineer
      def person(name, age, *args, city, job):
      print(name, age, args, city, job)
      def person(name, age, *, city='Beijing', job):
      print(name, age, city, job)
      >>> person('Jack', 24, job='Engineer')
      Jack 24 Beijing Engineer
      限定了kw中的关键字只能为city,job
  • 参数组合
    参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

  • 支持递归

高级特性

切片

1
2
3
4
5
6
7
8
L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
L[0:3]
L[:3]
L[-2:]
L[-2:-1]
L[:10:2]
L[::5]
L[:]

迭代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
... print(key)
...
a
b
c
>>> for k, v in d.items():
... print(k,v)
...
a 1
b 2
c 3
>>> for value in d.values():
... print(value)
...
1
2
3
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C

列表生成式

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
>>> import os # 导入os模块,模块的概念后面讲到
>>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode']
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']

生成器

1
2
3
4
5
6
7
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'

函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

小结

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

把list、dict、str等Iterable变成Iterator可以使用iter()函数:

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python的for循环本质上就是通过不断调用next()函数实现的,例如:

1
2
3
4
5
6
7
8
9
10
11
12
for x in [1, 2, 3, 4, 5]:
pass
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break

函数式编程

高阶函数

  • 传入函数
    1
    2
    def add(x, y, f):
    return f(x) + f(y)

map/reduce

  • map
    1
    2
    3
    4
    5
    6
    >>> def f(x):
    ... return x * x
    ...
    >>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
    >>> list(r)
    [1, 4, 9, 16, 25, 36, 49, 64, 81]
  • reduce
    reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
  • 两者综合
    1
    2
    3
    4
    5
    6
    7
    8
    from functools import reduce
    DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    def str2int(s):
    def fn(x, y):
    return x * 10 + y
    def char2num(s):
    return DIGITS[s]
    return reduce(fn, map(char2num, s))
  • filter
    1
    2
    3
    4
    5
    6
    7
    8
    def is_odd(n):
    return n % 2 == 1
    list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
    # 结果: [1, 5, 9, 15]
    def not_empty(s):
    return s and s.strip()
    list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
    # 结果: ['A', 'B', 'C']
  • sorted
    1
    2
    3
    4
    5
    6
    >>> sorted([36, 5, -12, 9, -21], key=abs)
    [5, 9, -12, -21, 36]
    >>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
    ['about', 'bob', 'Credit', 'Zoo']
    >>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
    ['Zoo', 'Credit', 'bob', 'about']

Android中遇到的问题

遇到了挺多的问题,但是每个问题都写篇文章感觉有点不实在,所以还是选择将这些小知识点都汇集到这一篇文章,方便自己再次查看吧。好多问题解决了之后没有及时记录下来,现在忘得差不多了。

替换Fragment的问题

这个问题遇到过好几次了,但是还是没有很快地解决它.不过最终还是解决了~所以还是记下来踩过的坑吧


现象

代码如下. 在我们看来这可能是再平常不过的代码了,但是它就是报错了,而且就是在replace这个函数这里. 它需要的就是一个Fragment呀,我的fragment也是一个继承了Fragment类, 为什么就不能完成类型匹配呢? 所以很是纠结

1
2
3
getFragmentManager().beginTransaction()
.replace(R.id.container, fragment)
.commit();

但是,我心里清楚,我的fragment是继承自android.support.v4.app.Fragment, 而且我还记得之前使用过一个叫做getSupportFragmentManager()的方法, 但是为什么在这个Activity里面就是调用不出来! 气愤啊, 但是想到了一个叫做AppCompatActivity的适用性高的类, 因此只能想到是不是只有support类型的Activity才有getSupportFragmentManager(). 让宿主Activity继承AppCompatActivity, 最后调用出了getSupportFragmentManager(), 解决了这个莫名其妙的问题!

总结

  • FragmentManager也有两种, 一个是android.support.v4.app包下的,一个是android.app包下的.
  • 继承自Activity的活动里面,只能获取到android.app.FragmentManager; 继承自AppCompatActivity才可以获得android.support.v4.app.FragmentManager
  • 不同包下面的FragmentManager只能替换继承自同一个包下面的Fragment.
  • 两个不同包下面的具体类如下所示

android.app包下的Fragment*

android.support.v4.app包下的Fragment*

Calendar中获取到的月份比实际月份少1

不算是bug吧。就像数组一样,月份也从0开始算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Field number for <code>get</code> and <code>set</code> indicating the
* month. This is a calendar-specific value. The first month of
* the year in the Gregorian and Julian calendars is
* <code>JANUARY</code> which is 0; the last depends on the number
* of months in a year.
*
* @see #JANUARY
* @see #FEBRUARY
* @see #MARCH
* @see #APRIL
* @see #MAY
* @see #JUNE
* @see #JULY
* @see #AUGUST
* @see #SEPTEMBER
* @see #OCTOBER
* @see #NOVEMBER
* @see #DECEMBER
* @see #UNDECIMBER
*/

SQLite中有两张表时出现SQLiteLog: (1) no such table

其实这个问题的出现,是对SQLiteOpenHelper没有了解清楚的一种表现。这段回答确实是醍醐灌顶。

SQLiteOpenHelper onCreate() and onUpgrade() callbacks are invoked when the database is actually opened, for example by a call to getWritableDatabase(). The database is not opened when the database helper object itself is created.

SQLiteOpenHelper versions the database files. The version number is the int argument passed to the constructor. In the database file, the version number is stored in PRAGMA user_version.

onCreate() is only run when the database file did not exist and was just created. If onCreate()returns successfully (doesn’t throw an exception), the database is assumed to be created with the requested version number. As an implication, you should not catch SQLExceptions in onCreate()yourself.

onUpgrade() is only called when the database file exists but the stored version number is lower than requested in constructor. The onUpgrade() should update the table schema to the requested version.

When changing the table schema in code (onCreate()), you should make sure the database is updated. Two main approaches:

  1. Delete the old database file so that onCreate() is run again. This is often preferred at development time where you have control over the installed versions and data loss is not an issue. Some ways to to delete the database file:
    • Uninstall the application. Use the application manager or adb uninstall your.package.name from shell.
    • Clear application data. Use the application manager.
  2. Increment the database version so that onUpgrade() is invoked. This is slightly more complicated as more code is needed.
    • For development time schema upgrades where data loss is not an issue, you can just use execSQL("DROP TABLE IF EXISTS <tablename>") in to remove your existing tables and call onCreate() to recreate the database.
    • For released versions, you should implement data migration in onUpgrade() so your users don’t lose their data.

出现这样的错误的情景为:

有两张表,开始的时候创建了一个**Helper继承自SQLiteOpenHelper,然后又需要创建一张表的时候,又创建了一个类继承自SQLiteOpenHelper,里面的数据库名相同,版本号相同,只有表名、创建的SQL语句不同。这样做的原因是因为以为每次都会执行onCreate(),然后表就被创建了。这样的想法是错误的,根本就不是这么一回事。没有好好看过数据库相关的啊~

在没看到这个回答之前,有过两次尝试,都解决了问题,但是为什么解决了,我竟然不知道!!

  • 尝试一:把两个数据库名改成不同的。这样就会在/data/data/**/databases/下面存在两个数据库文件。解决了问题。

  • 尝试二:后来感觉可能与数据库的版本有关系,所以这次不改数据库名,但是将后者的版本号提高,并重载onDowngrade()方法,让它不干任何事情。

  • 最终方案:将两个继承自SQLiteOpenHelper的类全部写到一个类里面,将另外一个删除掉。我觉得这是比较完美的解决方案,也大致明白了这背后的原因。好好地又上了一课。如下所示:

    代码展示

WebView显示中文网页乱码

很久之前也遇到过这个问题,但是到现在记得的也就是可以通过设置某些参数,然后就可以正常显示中文了。

这次还是直接把这个它的设置方法贴出来吧,让自己不用再找了。

1
2
3
webView.getSettings().setDefaultTextEncodingName("UTF -8");//设置默认为utf-8
//webView.loadData(data, "text/html", "UTF -8"); //API提供的标准用法,无法解决乱码问题
webView.loadData(data, "text/html; charset=UTF-8", null);//这种写法可以正确解码

String.replace()无法替换成功

其实这个问题挺奇怪的,但是也算不上一个问题吧。

这个方法并不会改变调用这个方法的String,而是返回一个替换了之后的String

写着写着忘记了这个,结果浪费了好久的时间。

RecyclerView如何创建ContextMenu

先上成功创建并获取到了所需信息的链接吧!

网上的说话基本上是ListView的,但是RecyclerView与它又不相同。因此按照网上的说法,基本上通过getMenuInfo()获取到的是空,好伤。如果不是空的话,那么会得到AdapterView.AdapterContextMenuInfo,这个里面包含了一些信息如position,应该是该项在整个RecyclerView中的位置吧。

网上的做法有两种,一种是:

  • 为ViewHolder设置setOnCreateContextMenuListener(),但是这样还是无法直接将所需要的信息传递进来,所以还需要设置setOnMenuItemClickListener(),用来处理点击该项后需要进行的事项,因此,这这里可以直接获取当前RecyclerView中的item并对其进行相关的操作。这个做法来自链接
1
2
3
4
5
6
itemView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
menu.add("稍后阅读").setOnMenuItemClickListener(item -> {
Logger.e(easyNews.getNewsId());
return false;
});
});
  • 可以参考这段代码,没有尝试过,但是mark一下吧!链接,这种做法挺靠谱的感觉。

    关键是下面这段代码,其余的可以按照ListView的那样进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class RecyclerViewImplementsContextMenu extends RecyclerView {
private AdapterView.AdapterContextMenuInfo contextMenuInfo;
public RecyclerViewImplementsContextMenu(Context context) {
super(context);
}

public RecyclerViewImplementsContextMenu(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}

public RecyclerViewImplementsContextMenu(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

@Override
public AdapterView.AdapterContextMenuInfo getContextMenuInfo() {
return contextMenuInfo;
}

@Override
public boolean showContextMenuForChild(View originalView) {
int position = getChildAdapterPosition(originalView);
long longId = getChildItemId(originalView);
contextMenuInfo = new AdapterView.AdapterContextMenuInfo(originalView, position, longId);
return super.showContextMenuForChild(originalView);
}
}

Intent中如何传递一个普通对象

在做小应用的时候遇到了这种问题,网上的解答也比较完整。
方式一:Serializable 方式
使用Intent 来传递对象通常有两种实现方式,Serializable 和Parcelable,我们先来学习一下第一种的实现方式。
Serializable 是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。至于序列化的方法也很简单,只需要让一个类去实现Serializable 这个接口就可以了。
比如说有一个Person 类,其中包含了name 和age 这两个字段,想要将它序列化就可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person implements Serializable{  
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

其中get、set 方法都是用于赋值和读取字段数据的,最重要的部分是在第一行。这里让Person 类去实现了Serializable 接口,这样所有的Person 对象就都是可序列化的了。
接下来在FirstActivity 中的写法非常简单:

1
2
3
4
5
6
Person person = new Person();  
person.setName("Tom");
person.setAge(20);
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("person_data", person);
startActivity(intent);

可以看到,这里我们创建了一个Person 的实例,然后就直接将它传入到putExtra()方法中了。由于Person 类实现了Serializable 接口,所以才可以这样写。
接下来在SecondActivity 中获取这个对象也很简单,写法如下:

1
Person person = (Person) getIntent().getSerializableExtra("person_data");  

这里调用了getSerializableExtra()方法来获取通过参数传递过来的序列化对象,接着再将它向下转型成Person 对象,这样我们就成功实现了使用Intent 来传递对象的功能了。

方式二:Parcelable
除了Serializable 之外,使用Parcelable 也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable 方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent 所支持的数据类型,这样也就实现传递对象的功能了。
下面我们来看一下Parcelable 的实现方式,修改Person 中的代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Person implements Parcelable {  
private String name;
private int age;

@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
// TODO Auto-generated method stub
dest.writeString(name);
dest.writeInt(age);
}
public static final Parcelable.Creator<Person> CREATOR=new Parcelable.Creator<Person>() {

@Override
public Person createFromParcel(Parcel source) {
// TODO Auto-generated method stub
Person person=new Person();
person.name=source.readString();
person.age=source.readInt();
return person;
}

@Override
public Person[] newArray(int size) {
// TODO Auto-generated method stub
return new Person[size];
}
};

}

Parcelable 的实现方式要稍微复杂一些。可以看到,首先我们让Person 类去实现了Parcelable 接口,这样就必须重写describeContents()和writeToParcel()这两个方法。其中describeContents()方法直接返回0 就可以了,而writeToParcel()方法中我们需要调用Parcel的writeXxx()方法将Person 类中的字段一一写出。注意字符串型数据就调用writeString()方法,整型数据就调用writeInt()方法,以此类推。
除此之外,我们还必须在Person 类中提供一个名为CREATOR 的常量,这里创建了Parcelable.Creator 接口的一个实现,并将泛型指定为Person。接着需要重写createFromParcel()和newArray()这两个方法,在createFromParcel()方法中我们要去读取刚才写出的name 和age字段,并创建一个Person 对象进行返回,其中name 和age 都是调用Parcel 的readXxx()方法读取到的,注意这里读取的顺序一定要和刚才写出的顺序完全相同。而newArray()方法中的实现就简单多了,只需要new 出一个Person 数组,并使用方法中传入的size 作为数组大小就可以了。
接下来在FirstActivity 中我们仍然可以使用相同的代码来传递Person 对象,只不过在SecondActivity 中获取对象的时候需要稍加改动,如下所示:

1
Person person = (Person) getIntent().getParcelableExtra("person_data");  

注意这里不再是调用getSerializableExtra()方法,而是调用getParcelableExtra()方法来获取传递过来的对象了,其他的地方都完全相同。这样我们就把使用Intent 来传递对象的两种实现方式都学习完了,对比一下,Serializable的方式较为简单,但由于会把整个对象进行序列化,因此效率方面会比Parcelable 方式低一些,所以在通常情况下还是更加推荐使用Parcelable 的方式来实现Intent 传递对象的功能。

作为一名Android开发人员,时常遇到Android Studio抽风的情况。之前也遇到过,没有记录,之后就忘了,还得去重新去查解决办法,真的是有点痛心疾首。所以在这里特地记录下,在开发过程中,所遇到的一些关于AS的一些问题,让自己进步得更快。

Error type 3 as中更换包名后出现的问题

离奇事件的现场图

原因

修改了原本的包名.

现象1

之后R文件也出现了问题, 这个现场截图已经找不到了, 大致是这样的. 所有代码中应用了R文件的地方都出现了错误,并且将鼠标移到其上,可以通过Alt + Enter导入R文件.
但是这个R文件是上一个包名下的R文件, 导入了也没用, 还是该报错的地方报错. 后来, 找到了Manifest.xml文件中的package 属性, 发现它是修改包名之前的包名, 所以改了之后, rebuild了一下, 就解决了
修改这里就好啦

现象2

如上图所示. 所有的代码基本上修改好了, 开开心心的点了一下Run,结果给了我一大段红色的error…面对这个确实也比较无奈.我感觉这个应该与应用配置有关系, 也就是与那一堆Gradle Scripts有关系, 但是不知道该修改哪里的哪个参数.不过网上还是有比我先遇到这个问题的人, 解决方案也有了~如下:

I had the same error after renaming/refactoring. What I did was add the applicationId property attribute to my build.gradle file, and set its value to the application package. Like this:

1
2
3
4
5
android{
defaultConfig{
applicationId "com.example.mypackage"
}
}

from Stack Overflow

Enable Jack

直接上方法吧。引用自:Stackoverflow

The details on what is required to use Jack and how can be found in the documentation.

Here is the relevant part from the docs that goes in build.gradle on how to use jackOptions and set the compileOptions for java 1.8.

android {

defaultConfig {

jackOptions {
enabled true
}
}

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

}
UPDATE

The Jack toolchain is now considered deprecated according to this post and work is being done to natively support Java 8 features as part of the Android build system in the coming weeks according to the post.

The post also mentions that there should be little to no work migrating from Jack to the new method in case you still wanted to try enabling Java 8 features with Jack.

UPDATE 2 Preview Built-in Support

You can now try out the new built-in support for Java 8 using the latest Android Studio preview 2.4 preview 6.

For more information on how to enable it or migrate from Jack or Retrolambda see the documentation.

Gradle版本降级

导入TO-DO-MVP这个Google官方给的例子时,需要把Android Studio的Gradle插件版本从3.0.0-alpha4的版本降为 2.3.3。操作时,碰到了一个问题如下:

1
2
3
Error:(28, 0) Could not find method implementation() for arguments [com.android.support:appcompat-v7:25.3.1] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
Please install the Android Support Repository from the Android SDK Manager.
<a href="openAndroidSdkManager">Open Android SDK Manager</a>

其实这两个版本间Gradle的一些关键词有一些不同,改回2.3.3之后,需要将关键词也改回来。有点想吐槽~这都不兼容了!

基本上是把Iementation改回Compile就好了,注意一些地方的大小写。

Android Studio显示No debuggable process

正在开发的程序已经被运行起来了,可是这里却显示没有debuggable process。
现场

修改方法真的是一语道破天机啊~

You also should have Tools->Android->Enable ADB Integration active.

Android Studio中Error:String index out of range: 0

现场

出现这种错误有点莫名其妙,网上上的解释是values下面的文件有出错的情况,检查了每个文件,都没出现<string name=""></string> 这种类型的情况。很是苦恼,网上的说法也基本上与此种情况类似。
在检查到gradle.properties这个配置文件的时候,发现了git无法自动合并而让我们手动解决合并冲突的痕迹,而此冲突却并没有被手动解决。如下图所示:

祸首

删掉了不必要的东西之后,一切正常。

出现这种错误,有点不应该。但是同时也说明了,网上的东西只能当参考啊!

常用linux命令

md5summd5sum命令采用MD5报文摘要算法(128位)计算和检查文件的校验和。一般来说,安装了Linux后,就会有md5sum这个工具,直接在命令行终端直接运行。MD5算法常常被用来验证网络文件传输的完整性,防止文件被人篡改。MD5 全称是报文摘要算法(Message-Digest Algo
阅读更多

通过hierarchyview探寻flowlayout

前言在开发过程中遇到了一个很熟悉的控件,但是我不知道它叫啥名字,并且也不知道该用什么样的语言去描述它。然而,我却在很多的应用中看到了它的身影,QQ音乐,YouTube等,如下图所示:为了一探究竟,我猜想它属于RecyclerView,是RecyclerView的一种定制化。通过勾选开发者选项中的显示
阅读更多

git使用笔记

记录自己在使用git中所用到的命令,算是半个笔记吧~

git merge & git rebase

参考

git初次运行时的配置

三个地方

  • 系统级别,/etc/gitconfig, 修改此选项时需要加上 --system选项
  • 当前用户级别,~/.gitconfig, 修改此选项时需要加上--global选项
  • 当前仓库级别,.git/config。
    低级别覆盖高级别的配置信息。

配置信息

  • 用户名 git config --global user.name "John Doe"
  • 用户邮箱 git config --global user.email [email protected]
  • 编辑器 git config --global core.editor vim

查看信息

  • 查看所有配置信息 git config --list
  • 查看某项配置信息 git config user.name

忽略文件

.gitignore文件忽略不想进行版本控制的文件。
参考https://github.com/github/gitignore

命令的详情

git add

三个作用:

  • 添加追踪
  • 添加到暂存区
  • 标记冲突文件状态为已解决

git status -s

如果嫌弃不带-s的命令输出的信息太繁杂,那么可以使用这个。
新添加的未跟踪文件前面有 ?? 标记,新添加到暂存区中的文件前面有 A 标记,修改过的文件前面有 M 标记。
出现在右边的 M 表示该文件被修改了但是还没放入暂存区,出现在靠左边的 M 表示该文件被修改了并放入了暂存区。

查看修改

git diff 查看尚未暂存的文件更新了哪些部分
git diff --staged 查看已暂存的将要添加到下次提交里的内容

提交更新

git commit

-m 添加一段信息,作为提交说明
-a 跳过暂存区,直接将已追踪的文件暂存起来并提交
–amend 此次提交的结果替代上次提交的结果

删除文件

git rm

从已跟踪文件清单中移除(确切地说,是从暂存区域移除),并连带从工作目录中删除指定的文件
-f 删除之前修改过并且已经放到暂存区域的文件
–cached 把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中

移动文件(或重命名)

git mv
等价于

1
2
3
mv 
git rm
git add

查看提交

git log

-p 显示每次提交的差异
-p -2 显示最近两次的差异
–stat 每次提交的简略的统计信息
–graph 显示 ASCII 图形表示的分支合并历史。
–pretty使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和format(后跟指定格式)。

移除暂存文件

git reset HEAD <file>... 将文件从暂存区移除

撤销文件的修改内容

git checkout -- <file>... 将此文件做的文件全部撤销

使用总结

git基本命令

git init 在执行命令的目录下创建git仓库

git add * 添加所有的文件到缓存区

git commit * -m 提交所有追踪的文件到git仓库

git pull origin master 从远程仓库拉代码到本地git仓库

git push origin master 将自己的代码推送到远程git仓库

创建新的分支

git branch 显示所有的分支

git branch -a 显示所有的分支包括远程的分支

git checkout -b branch_name 创建branch_name分支并切换到该分支上

git checkout branch_name 切换到branch_name分支上

关于.gitignore文件

这个文件顾名思义是起到忽略作用的,在git仓库中使用此文件,将不需要添加进git仓库的文件排除在外。可是在使用的时候,会遇到向其中添加了文件名,却
不起作用的情况。很奇怪,参考网上的说法,当已经添加该文件进入git仓库后,再在.gitignore中除去该文件,就会遇到这种情况,我就是属于这种情况,这时需要将其从仓库中删除,然后再执行git add时就会忽略掉该文件..gitignore只对未追踪的文件有过滤效果。可参考如下代码

1
2
3
git rm -r --cached .
git add .
git commit -m "comment"

关于本地分支与远程分支的链接关系

当从远程仓库上面拉下代码之后,其中有若干分支,如若想在本地建立一个分支,并使之与远程分支中的某个分支对应,那么该如何操作呢?

git checkout --track origin/branch_name local_branch_name 这个命令会自动创建local_branch_name,如果它已经存在了,那么将执行失败~

查看、删除远程仓库分支/Tag

试了试将所有的feature分支都推送到远程仓库,后来发现那个分支基本上没啥用,在将feature分支merge到dev分支上后,feature分支就一直处于当初的那个状态,当dev一直向前走的时候,你再次回到该feature分支,相当于回到了dev分支之前的某个节点,因此我认为将其推送到远程仓库是没有多大的意义的,所以动起了删掉所有本地和远程仓库中已merge的feature分支。

首先是查看远程仓库里面所含有的feature分支:

1
2
3
4
5
6
7
8
9
asahi@asahis-MBP  ~/AndroidStudioProjects/NHKNews   master  git branch -a
dev
feature-remote
* master
remotes/origin/HEAD -> origin/master
remotes/origin/dev
remotes/origin/feature-main-page
remotes/origin/feature-remote
remotes/origin/master

当使用git branch -d feature-main-page之后,得到的结果如上,可是我只是删除了本地的分支,远程的分支依然还在,该如何删除这个远程仓库里面的分支呢?有git命令为git push origin --delete origin/feature-main-page,可是出现错误,不能删除,因此试了另外一个命令git push origin :feature-main-page,成功删除。然后对于feature-remote分支,直接使用前条命令,可将本地与远程仓库里面的分支一起删除.

再次使用git remote show origin,查询得到的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
* remote origin
Fetch URL: [email protected]:xuchuanjun/NHKNews.git
Push URL: [email protected]:xuchuanjun/NHKNews.git
HEAD branch: master
Remote branches:
dev tracked
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local refs configured for 'git push':
dev pushes to dev (up to date)
master pushes to master (up to date)

总结:超强的总结

crossGFW玩具档案

这是一个玩具脚本,能在windows和linux上面跑。当时有一个网站提供免费的ss账号,但是账号、密码会定时变更,所以写了这个脚本来爬取免费vpn账号,然后配置好参数,最后双击启动shadowsocks。
好傻吊的玩具,被我校招时一直写简历上面,哈哈。放在 repo 里面,感觉不值得,以文章的形式留个纪念吧!

项目树状结构图:

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── Shadowsocks.exe
├── crossGFW.jar
├── gui-config.json
├── src
│   ├── GetData2Json.java
│   ├── Main.java
│   ├── Server.java
│   └── StartProxy.java
├── sss.bat
└── statistics-config.json

1 directory, 9 files

入口

入口很简单,双击 sss.bat 脚本,代理就自己挂上了。但是貌似在jar包里面也启动了ss代理,搞不懂啊。

1
2
3
java -jar crossGFW.jar
type gui-config.json
pause

抓取免费账号

现在的疑问为什么当时不用Python写,非得用Java。

此处的入口是一个Main方法,它主要抓取免费账号、填充vpn配置和启动ss。

1
2
3
4
5
6
7
8
9
/**
* Created by fcy on 2017/3/6.
*/
public class Main {
public static void main(String[] args) {
GetData2Json.getJson();
StartProxy.start();
}
}

抓取免费账号与填充配置

主要逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Created by fcy on 2017/3/6.
*/
public class GetData2Json {
private static List<String> urlList ;
private static List<Server> serverList = new ArrayList<Server>();
static{
urlList = new ArrayList<String>();
urlList.add("http://www.ishadowsocks.net");
urlList.add("https://freessr.xyz");
}
public static String getHTML(String url){
StringBuffer sb = new StringBuffer();
BufferedReader br;
String line = null;
try{
URL url1 = new URL(url);
URLConnection conn = url1.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36");
conn.connect();
br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
}catch (Exception e){
e.printStackTrace();
}finally {
return sb.toString();
}

}
private static void initData(){
String re = " <h4>[ABC]服务器地址:([^<]*)</h4>\n" +
" <h4>端口:([^<]*)</h4>\n" +
"<h4>[ABC]密码:([^<]*)</h4>\n" +
" <h4>加密方式:([^<]*)</h4>\n";
String HTML;
HTML = getHTML(urlList.get(0));
Pattern p = Pattern.compile(re);
Matcher m = p.matcher(HTML);
while(m.find()){
serverList.add(new Server(m.group(1),m.group(2),m.group(3),m.group(4)));
}
re = "\\s*<h4>[A-Z]*服务器地址:([^<]*)</h4>\n" +
"\\s*<h4>端口:([^<]*)</h4>\n" +
"\\s*<h4>密码:([^<]*)</h4>\n" +
"\\s*<h4>加密方式:([^<]*)</h4>";

HTML = getHTML(urlList.get(1));
Pattern p2 = Pattern.compile(re);
Matcher m2 = p2.matcher(HTML);
while(m2.find()){
serverList.add(new Server(m2.group(1),m2.group(2),m2.group(3),m2.group(4)));
}
}
public static void getJson() {
String os = System.getProperty("os.name").toLowerCase();
initData();
StringBuffer sb = new StringBuffer();
for (int i = 0;i<serverList.size();i++){
sb.append(serverList.get(i));
if(i<serverList.size()-1){
sb.append(",\n");
}else{
sb.append("\n");
}
}
if(os.contains("windows")){
StringBuffer sb1 = new StringBuffer();
sb1.append("{\n" +
"\"configs\" : [\n");
sb1.append(sb.toString());
sb1.append("],\n" +
" \"strategy\": null,\n" +
" \"index\": 0,\n" +
" \"global\": true,\n" +
" \"enabled\": true,\n" +
" \"shareOverLan\": false,\n" +
" \"isDefault\": false,\n" +
" \"localPort\": 1080,\n" +
" \"pacUrl\": null,\n" +
" \"useOnlinePac\": false,\n" +
" \"availabilityStatistics\": false,\n" +
" \"autoCheckUpdate\": true,\n" +
" \"isVerboseLogging\": false,\n" +
" \"logViewer\": {\n" +
" \"fontName\": \"Consolas\",\n" +
" \"fontSize\": 8.0,\n" +
" \"bgColor\": \"black\",\n" +
" \"textColor\": \"white\",\n" +
" \"topMost\": false,\n" +
" \"wrapText\": false,\n" +
" \"toolbarShown\": false,\n" +
" \"width\": 600,\n" +
" \"height\": 400,\n" +
" \"top\": 328,\n" +
" \"left\": 766,\n" +
" \"maximized\": true\n" +
" },\n" +
" \"proxy\": {\n" +
" \"useProxy\": false,\n" +
" \"proxyType\": 0,\n" +
" \"proxyServer\": \"\",\n" +
" \"proxyPort\": 0,\n" +
" \"proxyTimeout\": 3\n" +
" },\n" +
" \"hotkey\": {\n" +
" \"SwitchSystemProxy\": \"\",\n" +
" \"ChangeToPac\": \"\",\n" +
" \"ChangeToGlobal\": \"\",\n" +
" \"SwitchAllowLan\": \"\",\n" +
" \"ShowLogs\": \"\",\n" +
" \"ServerMoveUp\": \"\",\n" +
" \"ServerMoveDown\": \"\"\n" +
" }\n" +
"}");
try {
FileWriter fw = new FileWriter("gui-config.json");
fw.write(sb1.toString());
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}else if(os.contains("linux")){
try {
FileWriter fw = new FileWriter(".config.json");
fw.write(serverList.get(4).toString());
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}else{
System.out.println("there is no solution yet!");
}

}
}

没搞明白当时的想法,我为啥还定义了一个 Server POJO类,代码里面并没见到调用呀。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Created by fcy on 2017/3/6.
*/
public class Server {
String server,server_port,password,method,remarks,auth,timeout;
String local_port;

@Override
public String toString() {
return "{" +
"\"server\": \"" + server + "\",\n" +
"\"server_port\": " + server_port + ",\n" +
"\"password\": \"" + password + "\",\n" +
"\"method\": \"" + method + "\",\n" +
"\"remarks\": \"" + remarks + "\",\n" +
"\"auth\": " + auth + ",\n" +
"\"timeout\": " + timeout + ",\n" +
"\"local_port\": "+local_port+"\n"+
'}';
}

public Server(String server, String server_port, String password, String method) {
this.server = server;
this.server_port = server_port;
this.password = password;
this.method = method;
this.remarks = "";
this.auth = "false";
this.timeout = "10";
this.local_port = "5555";
}
}

启动ss

最魔幻的事情还是发生了,与sss.bat里面的代码貌似有点冲突。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.io.IOException;

/**
* Created by fcy on 2017/3/6.
*/
public class StartProxy {
public static void winStart(){
Runtime rt = Runtime.getRuntime();
Process p = null;
try {
p = rt.exec("cmd");
p = rt.exec("shadowsocks.exe");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(p.toString());
}
public static void linuxStart(){
Runtime rt = Runtime.getRuntime();
Process p = null;

try {
p=rt.exec("(nohup sslocal -c .config.json > .iss.log &)");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(p.toString());
}
public static void start(){
String os = System.getProperty("os.name").toLowerCase();
if(os.contains("windows")){
winStart();
}else if(os.contains("linux")){
linuxStart();
}else{
System.out.println("there is no solution yet!");
}
}
}

也许是后面加的Java代码,但是 who cares,反正它也没有什么实际价值与维护的意义,当做纪念吧~

无U盘安装Linux openSUSE(通过硬盘安装Linux)

无U盘安装Linux openSUSE(通过硬盘安装Linux)一、说明为什么会想着用硬盘安装Linux?只是因为我陆陆续续买了两个U盘,然后它们都丢了,就没再买了。然而现在又想装个openSUSE,没有U盘,只能想办法通过硬盘安装。记录自己走过的弯路,同时也为大家提供一个无U盘或硬盘安装Linux
阅读更多

汇编语言DEBUG的使用

【汇编语言】DEBUG的使用在masm for windows中,需要先生存exe文件,然后再点调试按钮。常用的命令有:R命令:查看、改变CPU寄存器的内容;如果要修改某个寄存器的内容,可以在r的后面接上空格和寄存器名。如:-r ax,然后再输入需要修改的值。如下T命令:执行一条机器指令;D命令:查
阅读更多

汇编语言新手第一步——HelloWorld & A+B

汇编语言新手第一步——HelloWorld & A+B国际惯例,HelloWorld。这个程序是masm for windows里面的样例程序。按照我自己的理解,对其加上了注释。;完整段的Hello World程序DATAS SEGMENT STRING DB 'Hello W
阅读更多