远程调用

首先,定义一下本文中的几个概念:

远程调用:跨进程的方法调用
客户进程:发起方法调用的进程
服务进程:实际实现方法的进程

如果需要设计一个“远程调用”的机制,我们需要考虑以下几个问题:

  1. 客户进程如何发起方法调用
  2. 服务进程如何知道要调用的方法名,如何接收方法参数
  3. 客户进程与服务进程如何通信

基础模式如下:

1

Read More

AndroidStduio错误记录

系统:Ubuntu

java本版:1.7

1. 启动的时候“Plugin com.intellij failed to initialize and will be disabled null”

解决办法:将java的安装位置更改到/usr/java或者/opt/java就行了,注意同时修改原本的JAVA_HOME系统变量。

2. 下载的Android Studio自带了SDK,但是系统原本安装有SDK,所以删除新的SDK之后报错 “Your Android SDK is out of date or is missing templates. Please ensure you are using SDK version 22 or later.”

解决办法:在SDK Manager中将tools(包括sdk tools,platform tools,build tools)更新到最新的版本。然后

Configure -> Project Defaults -> Project Structure

在Project 设定Project SDK。在SDKs设定Buildtarget。 保存之后就可以“New Project”了

3. 修改包名之后 Activity class does not exist

问题描述:

修改了 applicationId 之后,无法从 As 界面 launcher Activity 。提示错误:

Activity class {package/xxxxx} does not exist

这个应该是 As 自身的问题,没有刷新缓存,把 .idea *.iml 什么都删除一下,重新打开基本就可以了。

4. 无法关联源码

File -> Settings (ctrl+alt+s) -> Appearance & Behavior -> System Settings -> Android SDK.

点击 edit,无论有没有下载过对应的源码,都执行一遍,主要是让 Android studio 刷新自身的信息。

支持 java8

最新有的库是使用 java8 编译的,有可能报字节码解析错误,所以可以都改成 java8 编译。

要求 as 2.1 以上, jack 支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android {
...
defaultConfig {
...
jackOptions {
enabled true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}


App错误记录

1. INSTALL_PARSE_FAILED_NO_CERTIFICATE

解决方法:在签名时,添加参数

-digestalg SHA1 -sigalg MD5withRSA

示例如下:

jarsigner -digestalg SHA1 -sigalg MD5withRSA -keystore my_keystore -signedjar $signed_apk $unsign_apk my_alias_name

2. AppCompat does not support the current

最新的Surpport里面的主题检查好像更严格了。以前使用

ActionBarActivity + Toolbar

的时候,我是这么定义的

1
2
3
4
<style name="AppTheme.Base" parent="Theme.AppCompat.Light">
<item name="android:windowNoTitle">true</item>
<item name="windowActionBar">false</item>
</style>

升级之后就出问题了。应该使用

1
2
3
4
<style name="AppTheme.Base" parent="Theme.AppCompat.Light">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
</style>

其实最好的方法还是,不管在哪里,都使用sdk预置的主题:

1
2
<style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
</style>

其他问题类似

3. Can not perform this action after onSaveInstanceState

比较常见的情况是 Activity 发起一个异步调用,然后 Activity 退到后台,异步调用返回之后,想弹出一个 Dialog 或者 DialogFragment,这个时候就会报

Can not perform this action after onSaveInstanceState

原因就是 Activity 调用 onStop (有的版本是 onPause) 之前,先触发了 onSaveInstanceState。

解决方案:

如果是在 Fragment 里面弹出 Dialog,可以判断宿主 Fragment 的当前状态,比如:

1
2
3
if(fragment.isResumed()){
dialog.show();
}

如果是 Activity,需要自己来实现当前状态检测,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class BaseActivity extends Activity{
boolean isResumed = false;
@Override
public void onResume(){
super.onResume();
isResumed = true;
}
@Override
public void onPause(){
super.onResume();
isResumed = false;
}
@Override
public void isResumed(){
return isResumed;
}
}

这里有个详细的说明:

http://www.cnblogs.com/kissazi2/p/4181093.html

3. 同样 Can not perform this action after onSaveInstanceState

注意 : onBackPressed() 与 finish() 的区别

finish() 这个方法比较纯粹,触发 Activity 生命周期中的 onDestroy 方法。

onBackPressed() 是在 Fragment 引入之后才新增加的方法,所以,onBackPressed() 相比 finish() 会先处理各种 Fragment 的状态。
这样问题就来了,假如按下 Home 键将当前 Activity 放到后台,这个时候某个调用触发 onBackPressed(),会进行状态清理,但是由于此时已经调用了 onSaveInstanceState,
所有涉及到 Fragment 的操作都会导致崩溃。由于 onBackPressed() 是默认方法,除非重写,不然也办法使用 commitAllowingStateLoss() 方法。

4. Java IOException App:transformClassesWithDexForApp_wandoujiaStage

这个错误出现在用 gradle 命令行打包的时候。

我遇到的一个原因就是在 Mac 上修改了类的位置之后,各个模块下的 build 文件没有 clean。

当用命令行来编译的时候,就会报 IOException 的错误。

解决方案:

先在工程根目录调用一下

1
gradle clean

然后再正常编译就行。

5. values 语言不完整

一个真实的错误,错误原因:

res
|
|----values :
|    | 
|    |----<null> //这里丢失了 cll_update_strings.xml 
|    |    
|----values-zh : 
|    |    
|    |----cll_update_strings.xml

如果将系统调成英文,那么会直接报错。

如何预防:

  1. 保证 values 的完备!
  2. 启动更严厉的 IDE 和 打包时检查

6. drawable 资源不完整

当前手机屏幕分辨率为 normal, 如果只在 drawable-normal 放有 xxx.png 资源文件,那么会崩溃。
因为 Android 如果找不到最佳匹配的资源图片,只会向更低级的进行查找,而不会向更高级的进行查找。

7. Execution failed for task ‘:xxx:transformClassesAndResourcesWithProguardForRelease’.

一般是因为 Proguard 配置错误或者不全

android.view.GLES20Canvas.clipPath

低版本开启硬件加速导致的 2D 绘图问题,参见官网文档:https://developer.android.com/guide/topics/graphics/hardware-accel.html#unsupported

不过虽然官网说从 API 18 开始就恢复支持了,但是我在 4.4 以及 5.0 版本上都看到过此类崩溃,待调查。。

####Android分享 Q群:315658668

promise核心概念(一)

首先,要明确一点,Promise 是用来处理回调嵌套的问题,而不是用来处理回调本身的问题。

程序要素

通常,我们的程序有三个要素:

  1. 执行器
  2. 上下文
  3. 表达式

执行器按照语义规则在上下文中执行表达式,而表达式的最终效果都是要修改上下文,不修改上下文的表达式除了浪费时间之外,是没有意义的。 方法是表达式的集合,方法对上下文的修改可以表现在两个方面:

  1. 在方法体中直接修改了上下文的状态。
  2. 产生一个返回值,参与到接下来的表达式中。

所以,执行一个方法,最终的意义就是观察这个方法对上下文的修改结果,然后做出相应的动作。

同步与异步

同步方法的特征:

  1. 有直接的返回值,没有返回值的方法肯定直接修改了上下文。
  2. 可预期的异常栈,异常会沿着当前调用链向上传播。

异步方法的特征:

  1. 运行在另一个执行过程中,因此,异常不会沿着当前调用连向上传播。
  2. 没有直接的返回值,只能通过回调函数来观察修改结果。

对比下面的例子:

同步:

    var value = fn_1();
    var c = value_1 + 20;
    var value_2 = fn_2(c);
    console.log(value_2);
执行器 A 发起 fn_1 的调用, 执行器 A 执行并等待 fn_1 的执行完成,执行器 A 观察 fn_1 的结果。
执行器 A 发起 fn_2 的调用, 执行器 A 执行并等待 fn_2 的执行完成,执行器 A 观察 fn_2 的结果。

由于 fn_1 和 fn_2 都是同步方法,这个过程会很完美的工作。 但是,如果 fn_1 和 fn_2 都是异步方法,而异步调用根本就没有返回值,所以,这段代码是得不到预期效果的。 那我们就只能这么写了:

异步:

fn_1(function(value_1){
    var c = value_1 + 20;
    fn_2(c, function(value_2){
        console.log(value_2);
    })
})

虽然写法不一样,但是本质都是一样的,注意其中执行器的变化:

执行器 A 发起 fn_1 的调用,执行器 B 执行并等待 fn_1 的执行完成,执行器 A 观察 fn_1 的结果。
执行器 A 发起 fn_2 的调用,执行器 C 执行并等待 fn_2 的执行完成,执行器 A 观察 fn_2 的结果。

既然本质都是一样的,那么我们就可以将 “执行并等待 -> 观察”封装为一个概念——Promise,当异步方法的结果返回时,根据不同的结果,触发不同的动作。 形式如下:

var promise_1 = create_promise(fn_1);

var promise_2 = promise_1.then(function(value_1){
    var c = value_1 + 20;
    return create_promise(c, fn_2);
});

promise_2.then(function(value_2){
    console.log(value_2);
});

如此一来,就将异步的嵌套回调,转化为扁平的同步调用形式。

C++与Java简单对比

类访问权限
C++ Java
类默认权限 私有 包访问
私有 private private
受保护 protected protected
公开 public public
友元 friend 不支持
继承体系
C++ Java
多态方法 virtual(虚函数) 非静态,非私有方法
要求子类实现的方法 pure virtual(纯虚函数) abstract(抽象方法)
接口 完全纯虚函数 interface
多重继承 支持 不支持

【Linux】各种配置记录

Python

pip

安装

1
2
3
sudo apt-get install python-pip
#或者
sudo aptitude install python-pip

virtualenv

安装

1
sudo pip install virtualenv

创建虚拟环境

1
virtualenv test_env

默认情况下,虚拟环境会依赖系统环境中的site packages,就是说系统中已经安装好的第三方package也会安装在虚拟环境中,
如果不想依赖这些package,那么可以加上参数

--no-site-packages 

进入虚拟环境

source ./bin/activate

退出虚拟环境

deactivate

supervisor

pip install supervisor

tornado

pip install tornado

autoreload

1
2
3
ioloop = tornado.ioloop.IOLoop.instance()
tornado.autoreload.start(ioloop)
ioloop.start()

Docker

Ubuntu 14.04

安装最新

1
2
3
4
5
6
7
8
9
10
11

sudo apt-get install apt-transport-https
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
sudo sh -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"
sudo apt-get update

apt-get install -y lxc-docker #安装

apt-get update -y lxc-docker #更新

ln -sf /usr/bin/docker /usr/local/bin/docker

lua

环境 Ubuntu 14.04

ubuntu 下 lua 的安装包,binary和dev是分开装的。

1
2
sudo apt-get install lua5.2
sudo apt-get install liblua5.2-dev

c 调用 lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

int main(int argc, char const *argv[]){
char buff[256];
int error;
lua_State *L = luaL_newstate();
//...
lua_close(L);
return 0;
}

第一个错误:

lua.h: No such file or directory

没有找到 h 头文件,需要指定:

gcc lua_test.c -I/usr/include/lua5.2

第二个错误:

undefined reference to `luaL_newstate'

没有找到 so 文件,需要指定:

gcc lua_test.c -I/usr/include/lua5.2 -llua5.2

找到文件位置:

locate lua.h
locate liblua

数据库工具 Cupboard

Cupboard 官方托管

翻译与示例托管

Cupboard for Android

Cupboard 是针对 Android 的一个简单的持久化存储方案,简单而且容易与现有代码集成。

更准确的说, Cupboard 是一个存取对象的方式。并不是一个真正的ORM,因为为了保持简单,它并不会去维护对象之间的关系。

设计理念

设计 Cupboard 是因为现有的持久化框架并不能满足实际的需求:

  1. 想要一个非侵入的:不必要继承某个特殊的Activity,你的 model 也不必要无实现某个特殊的接口,如果不愿意,都不必要实现 DAO 模式
  2. 需要一个通用的选择:在整个应用中都可以使用所定义的 model 对象,而并不局限于数据库
  3. 需要完美适应 Android 自有的类,比如 Cursor 以及 ContentValues,这样,可以在任何时候回退到 Android 框架里面。

使用方式/Using Cupboard

引入 Cupboard 依赖,然后静态导入 cupboard():

build.gradle:

compile 'nl.qbusict:cupboard:(insert latest version)'
最新是 2.1.4 所以可以这么写: compile 'nl.qbusict:cupboard:2.1.4'

java 类:

import static nl.qbusict.cupboard.CupboardFactory.cupboard;

在代码中可以这么调用:

public long storeBook(SQLiteDatabase database, Book book) {
    return cupboard().withDatabase(database).put(book);
}

上面的代码将一个 Book entity 存入数据库中,然后返回记录的 id, 就这么简单。

Entities

Cupboard 中的 entity 就是一个POJO,Cupboard使用反射来操作字段,没有使用注解,因为 Android 中的注解反射实在是太低效了。

entity 在使用之前,需要先使用 Cupboard.register() 进行注册。一个示例:

public class Book {
    public Long _id;
    public String title;
    public Author author;
    public Date publishDate;
}

entity 的字段名对应 SQLite 数据库中的 列名,表名根据 entity 名得来,本例中,表名就是 book。
每个 entity 都应该有一个 Long 类型的 _id 字段,采用 Long 类型是因为这样 _id 就可以取 null了。
而且,Android 原生的 Cursor 本身就期望一个 _id。
entity 的一个字段可以是任何基本类型,相应的包装类,java.util.Date,或者另一个 entity 。

Operations

你可以:

  1. 使用 put 来创建或者更新一个 entity
  2. 使用 get 来获取一个 entity
  3. 使用 delete 来删除一个 entity
  4. 使用 query 来查询 entity

当使用 withDatabase() 【见后文】的时候,也可以像使用 SQL 一样来执行 update 操作。

Compartments

在许多通常的应用中,你需要从不同的组件访问 model,比如 ContentProvider 可能就持有 SQLiteDatabase 的连接,然后你从 Activity 或者 Service 来访问。

如果你只能在 ContentProvider 使用持久化库,而在其他组件里面只能使用原始的 ContentValues, 这不就是一个笑话吗。

Cupboard 以及预料到这一点:

SQliteDataBase db = getDatabase();

// 将一个 entity 保存到数据库中
cupboard().withDatabase(db).put(book);
Cursor cursor = getCursor();

// 从 cursor 获取第一个条记录
Book book = cupboard().withCursor(cursor).get(Book.class);

// 遍历所有的记录
Iterable<Book> itr = cupboard().withCursor(cursor).iterate();
for (Book book : itr) {
  // do something with book
}

// 使用 content provider 来保存一条记录
Uri bookUri = ...
cupboard().withContext(this).put(bookUri, book);

//将 Book  entity 传递给另一个需要 ContentValues 对象参数的方法
ContentValues values = cupboard().withEntity(Book.class).toContentValues(book);

// 我们可能正在构建一组 ContentProviderOperation 
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(10);
Book newBook = new Book();
...
cupboard().withOperations(ops).put(bookUri, book).put(bookUri, newBook);

官方文档翻译

  1. Working_with_databases
  2. Working_with_ContentProviders
  3. Working_with_Cursors
  4. Working_with_existing_data_structures_and_annotation_support
  5. Custom_Converters
  6. ProGuard_configuration

Demo

参见工程 app

Q&A

_id的生成机制

注意每个 model 里面定义的 _id,Cupboard 要求使用 Long 类型,这样就可以根据 _id 是否为 null 来判断是否需要自动分配 _id。
当然,你也可以定义为 long 或者 int 等等任意其他类型的数值,但是,由于数值的默认值都是 0,因此,Cupboard 无法判断这个值到底是默认值还是用户赋值。
从而需要用户自己来为维护 _id 的分配。比如,定义如下:

public class SimpleBook {
    public long _id;
    public String title;
}

如果你使用下面的调用:

SimpleBook simpleBook = new SimpleBook();
simpleBook.title = "title_single_" + System.currentTimeMillis();
cupboard().withDatabase(cupboardSQLiteOpenHelper.getWritableDatabase()).put(simpleBook);

那么,不论你调用多少次,数据库里面只会得到一条数据,而且 _id = 0;所以除非你手动赋值 _id:

SimpleBook simpleBook = new SimpleBook();
simpleBook._id = System.currentTimeMillis();
simpleBook.title = "title_single_" + System.currentTimeMillis();
cupboard().withDatabase(cupboardSQLiteOpenHelper.getWritableDatabase()).put(simpleBook);

所以,如果 _id 没有特别的需求,还是按照 Cupboard 默认要求。

RoundedBitmapDrawable 创建圆角图片

简介

RoundedBitmapDrawable 是 android.support.v4.graphics.drawable 里面的一个类,用来创建简单的圆角图片。
如果只是简单的圆角展示,比如展示一个圆角头像,这个类完全可以胜任。

简单使用

  1. 获取 RoundedBitmapDrawable。RoundedBitmapDrawable 是一个抽象类,无法直接获取。所以提供了 RoundedBitmapDrawableFactory 来操作:

    RoundedBitmapDrawableFactory的静态签名:

     static RoundedBitmapDrawable	create(Resources res, InputStream is)
     static RoundedBitmapDrawable	create(Resources res, String filepath)
     static RoundedBitmapDrawable	create(Resources res, Bitmap bitmap)
     
    

    没有提供直接从 resId 获取的方法,但是是一样的:

     RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(), BitmapFactory.decodeResource(getResources(), R.drawable.xxx));
    
  2. 设置属性。几个重要的属性:

    1. cornerRadius:圆角半径
    2. alpha:透明度

    如果想得到一个圆形,那么可以直接调用 RoundedBitmapDrawable#setCircular(true),

    不过这样需要注意点是,如果原始的图形不是圆形,那么图形会变形。

    当然,结果还与 scaleType 有关,这里有点复杂,暂无需关心。所以,如果你想要一个圆形,你就给一个正方形。

    不要同时设置 cornerRadius 和 setCircular(true),因为两者是冲突的。

  3. 设置 Drawable。 ImageView#setImageDrawable(roundedBitmapDrawable);

原理

RoundedBitmapDrawable 内部使用 BitmapShader 来处理图形渲染,无他。

第三方开源控件

RoundedBitmapDrawable 的局限性还是比较大,如果想要实现一写些自由度更大的圆角,边框等等,可以考虑使用第三方空间,比如:

CircularImageView
RoundedImageView

Android 墙内操作

一,更新 SDK

各种工具参考 androiddevtools

腾讯 Bugly 使用比较流畅,推荐。

还是 中国科学院开源协会镜像站 的比较靠谱。。

二,编译 Android 源码

编译环境:

操作系统 Ubuntu 14.04.1 x86_64

更换源。ubuntu 自带的源容易发生版本不匹配的问题。

替换源(/etc/apt/source-list)为:

deb http://mirrors.163.com/ubuntu/ trusty main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ trusty-security main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ trusty-updates main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ trusty-proposed main restricted universe multiverse
deb http://mirrors.163.com/ubuntu/ trusty-backports main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ trusty main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ trusty-security main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ trusty-updates main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ trusty-proposed main restricted universe multiverse
deb-src http://mirrors.163.com/ubuntu/ trusty-backports main restricted universe multiverse

更新一下:

1
$ sudo apt-get updata

配置编译环境

1
2
3
4
$ sudo apt-get install git-core gnupg flex bison gperf build-essential \
zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 \
lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev ccache \
libgl1-mesa-dev libxml2-utils xsltproc unzip

各种安装依赖冲突的情况,都可以通过更换源解决。

安装 java

这个需要根据编译的源码版本而定,比如编译 Android 6.0 需要 OpenJDK 8。但是编译 Android 4.0 需要 Oracle Java 6.
具体安装过程略过。

下载源码

这里使用清华大学的镜像。

参考文章 清华大学 TUNA 镜像源,Android 镜像使用帮助

参考文章 同步、更新、下载Android Source & SDK from 国内镜像站

安装 Repo,

1, 添加 PATH

1
2
$ mkdir ~/bin
$ PATH=~/bin:$PATH

2, 下载 Repo

1
2
$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
$ chmod a+x ~/bin/repo

3., 修改repo

修改环境变量或者直接修改 /git-repo/repo 文件。

将 google 的地址

REPO_URL= 'https://gerrit.googlesource.com/git-repo'

改为清华大学的地址

REPO_URL= 'https://gerrit-google.tuna.tsinghua.edu.cn/git-repo'

4, 下载 manifest

1
$ repo init -u git://aosp.tuna.tsinghua.edu.cn/android/platform/manifest

或者选择版本

1
2
$ repo init -u git://aosp.tuna.tsinghua.edu.cn/android/platform/manifest -b android-4.0.1_r1

5, 同步

1
$ repo sync

编译

建立环境

1
$ source build/envsetup.sh

这个时候就可以使用 lunch,mmm等命令了

1
$ lunch 

lunch负责设置一些环境变量,比如 TARGET_PRODUCT 等等。
结果中 full 表示完全编译,eng表示工程版本,full-eng就是模拟器版本。

开始编译,指定4线程。

1
2
$ make -j4

注意编译过程中的 java 版本问题。

三,更新各种依赖库

暂时没有 jcenter 镜像,也别瞎折腾了,还是搞个 vpn 吧,毕竟党国无耻。