安装第三方App到电视盒子

安装第三方App方法1

  1. 准备一个U盘,注意用FAT格式(Windows能读写就OK)
  2. 将需要安装的App下载到U盘,文件后缀名必须是.apk
  3. 将U盘插入电视盒子,然后通过盒子自身带的文件管理App安装:如Magic Box:应用--> 本地播放,进入后选中U盘上的.apk文件即可安装

安装第三方App方法2

  1. 如果盒子不带USB盘,还可以用adb远程安装
  2. adb connect <your_magic_box_ip>
  3. adb install <you_app_to_install>.apk

直播和回放App

  1. 在电脑上用浏览器下载:http://app.shafa.com/shafa.apk 到U盘
  2. 按方法1安装好后,在“应用“中找到“沙发管家”
  3. 启动“沙发管家”,安装直播App:如“龙龙直播”,也有支持回放的App,如“电视猫视频”等。

Gradle的Properties

问题背景

团队一起在开发一个Android项目,工程师有的使用Eclipse,有个使用Intellij IDEA,有的使用Android Studio。每个人安装的Android SDK build-tools可能都不一样,有的是19.0.3,有的是19.1.0,不同版本的build-tools对Gradle Plugin也有相应的要求,如19.0.3对应的是com.android.tools.build:gradle:0.10.+,19.1.0对应的是com.android.tools.build:gradle:0.12.+,下面是一个典型的build.gradle配置文件。

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:0.10.+'
    }
}

apply plugin: 'android-library'

android {
    compileSdkVersion 19
    buildToolsVersion 19.0.3

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 19
    }
}

在合作开发中遇到的一个尴尬的问题是,IDEA最新版还不能很好的支持Gradle Plugin 0.12+,而Android Studio最新版则要求使用0.12+。大家又共用一个Git仓库。可能的解决方案是,从Git checkout出来的项目需要有一个基础的版本号,但是开发者可以在本地通过一处文件(不check in到git)来重载版本号。

解决方案

Gradle支持三种Properties, 这三种Properties的作用域和初始化阶段都不一样,下面分别列出了其部分特点。:

  1. System Properties:

    1. 可通过gradle.properties文件,环境变量或命令行-D参数设置 2. 可在setting.gradle或build.gradle中动态修改,在setting.gradle中的修改对buildscript block可见;
    2. 所有工程可见,不建议在build.gradle中修改
    3. 多子工程项目中,子工程的gradle.properties会被忽略掉,只有root工程的gradle.properties有效;
  2. Project Properties:

    1. 可通过gradle.properties文件,环境变量或命令行-P参数设置,优先级是:
    2. 可在build.gradle中动态修改,但引用不存在的project properties会立即抛错
    3. 动态修改过的project properties在buildscript block中不可见
  3. Project ext properties:

    1. 可在项目的build.gradle中声明和使用,本工程和子工程可见
    2. 不能在setting.gradle中访问

如果有多处设置,加载次序如下(注意:gradle 2.0是这样的, 1.10~1.12有bug), 后面的覆盖前面的设置

  1. from gradle.properties located in project build dir.
  2. from gradle.properties located in gradle user home.
  3. from system properties, e.g. when -Dsome.property is used in the command line.
  4. setting.gradle
  5. build.gradle

根据其特点,这里给出一个使用System Properties来解决问题的方案。

[Read More]

Android异步编程

Android的线程和内存模型

Android操作系统在boot后,会启动一个Zygote(受精卵)进程,Zygote进程负责创建大部分应用程序进程。Zygote进程启动加载核心程序库和数据结构到内存后会创建一个Dalvik虚拟机(DVM)进程--SystemServer,此进程会包含大部分的系统服务(包括管理Activity的服务ActivityManagerService),SystemServer初始化后,Zygote进程会侦听本地的socket端口, 等待进一步的指令。当新的app被启动时,Zygote会为这个app创建一个DVM—-直接fork出一个子进程,这种架构的好处是同时启动多个App时,多个App进程可以访问共享内存。

Android App的进程也是一个DVM,内部有许多线程在执行,比如,主UI线程(Main Thread),垃圾回收线程等。其中主UI线程负责执行我们写的应用代码。对于只做很少的I/O操作或耗时操作的App,单一线程开发模式问题不大,但是如果有大量IO或者CPU计算的任务,我们就必须在其他线程内完成了。

因为主UI线程需要根据硬件刷新率[^3]同步用户界面的重绘。手机应用体验流畅要求界面帧率[^3]达到每秒60,也就是说每16.67毫秒就需要重绘一帧,这意味着如果我们在主线程上执行的任务超过16毫秒,就会出现丢帧现象,也就是界面会开始变卡。。。

Android异步执行任务的方法有以下几种:

AsyncTask

AsyncTask是最常用的异步方法,功能结构设计的也很丰富,给使用者足够的控制,使用上主要是将异步执行的任务放在下面方法里。

protected Result doInBackground(Params... params)

然后调用.execute(params)方法即可。

AsyncTask的执行逻辑在API Level 3只能串行执行, 在API Level 4改成了最多128个线程的线程池执行,API Level 11则改成了缺省所有的AsyncTask是在一个线程中顺序执行的,这样可以保证执行和提交的次序一致,如果希望能并发的执行,可以用下面的方法在线程池内执行:

task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);

AsyncTask.THREAD_POOL_EXECUTORThreadPoolExecutor的一个实例,配置是最少5个线程,最多128个。如果需要自己来配置线程池大小,你可以传递自己配置的一个实例到上述方法。

使用AsyncTask需要注意的几个问题:

碎片化问题

因为不同版本的Android AsyncTask缺省执行逻辑并不一样,可能在不同机型上表现不一致。如果要自己控制AsyncTask的并发度,解决这个问题的建议是复制Android SDK的AsyncTask源码自己实现一个AsyncTask。

Activity生命周期问题

Activity可能早于AsyncTask执行完被销毁,如果AsyncTask还继续执行,有可能会浪费资源,并且如果AsyncTask里引用了Activity或部分的View Hierarchy,还会造成引用的对象不会被垃圾回收而引起内存泄漏。通常AsyncTask会定义为Activity的一个匿名inner class,这会建立一个隐式的引用到Activity。

解决的方法是:

  1. 在Activity里的onPause方法里及时取消不需要再执行的AsyncTask(这种方法在切换到横屏时会重启异步任务,有点浪费)
  2. 更好的是使用retained headless fragment来解决生命周期问题,具体演示代码见参考链接

Async比较合适的是较短的(1,2秒),CPU密集计算或读写文件等阻塞IO操作。耗时较长的网络调用用Async不是最合适的。

Handler & HandlerThread

Handler的异步编程是基于消息队列模型的。执行任务的线程称之为Looper线程,其他线程则将需要异步执行的任务发送给Looper线程–插入其消息队列,方法有:post(较方便使用,但每次需要创建新对象)或sendMessage(较高效,复用消息实例,适合执行大量类似的异步任务)

Looper

Looper和它的名字意思一样就是Looper线程会永远循环,当没有消息的时候,Looper线程(消费者)会使用(Object.wait)方法等待其他线程(生产者)插入新的任务消息,这时候其他线程(Object.notify)

Android的主线程其实就是一个Looper线程。

需要注意的是,使用Handler和AsyncTask一样,要注意匿名inner class对Activity的隐式引用而造成内存泄漏,所以使用的时候要记得清理;

解决方法是使用对使用的Activity中的View对象用Weak Reference,并处理当View对象为null的情况。

Handler适合更长一点的(>2秒)的异步任务处理。

Loader

Loader在Android编程框架中被广泛用于后台加载数据(从文件,数据库甚至网络)。

AsyncTaskLoader

具体的Loader实现。

CursorLoader

用户数据库数据后台加载。

Loader在使用上比较大的优势是和Activity的部分解耦,更见到的生命周期管理。

IntentService

IntentService是Service的一个实现类。其内部实现包含了一个HandlerThread,当任务提交给IntentService时,会被加到队列并顺序处理。

public class MyIntentService extends IntentService {
     public MyIntentService() {
       super("thread-name");
     }
     protected void onHandleIntent(Intent intent) {
       // executes on the background HandlerThread.
     } 
}

Intent intent = new Intent(context, MyIntentService.class);
intent.setData(uri); intent.putExtra("param", "some value"); 
startService(intent);

IntentService返回任务结果给Activity可以有下面几种方法(Service和Activity的通信方法):

[Read More]

提高Android开发效率的小贴士

查看日志 adb logcat

下面命令将只显示错误日志,和所有Tag=mytag的调试日志,-C 会用不同颜色区分不同级别的日志,但只有Android 4.3以后才支持。

adb logcat [-C] *:E <mytag>:D

远程调试 adb over TCP

首先在手机或Pad上执行以下命令(要求root)

su
setprop service.adb.tcp.port 5555
stop adbd
start adbd

再执行下面命令则可以看到手机的网络地址

netcfg | grep wlan

在电脑上则执行

adb connect <mobile_phone_ip> 5555
adb shell

安装运行

gradle installDebug && adb shell am start -n com.laiwang.protocol.android/.MainActivity

Over

拷贝Android应用的数据

有root权限

adb shell su -c cat /data/data/app.package.name/databases/application.sqlite | sed 's/\r$//' > application.sqlite

应用可调试的话

adb shell
run-as app.package.name \
cp /data/data/package.name/databases/application.sqlite /sdcard/
exit
adb pull /sdcard/application.sqlite ~/

使用备份方法

adb backup -f ~/data.ab -noapk app.package.name
dd if=data.ab bs=1 skip=24 | python -c "import zlib,sys;sys.stdout.write(zlib.decompress(sys.stdin.read()))" | tar -xvf -

参考链接

  1. http://blog.shvetsov.com/2013/02/access-android-app-data-without-root.html

使用夏普GP2Y1010AU0F灰尘传感器检测空气质量

夏普GP2Y1010AU0F灰尘传感器价格较便宜,能检测出室内空气中的灰尘和烟尘含量。另外还有韩国SYHITECH生产的DSM501A粉尘传感器也有类似功能。

检测原理

其原理如下图,传感器中心有个洞可以让空气自由流过,定向发射LED光,通过检测经过空气中灰尘折射过后的光线来判断灰尘的含量。

电路图

因为数据是通过pin 5的电压模拟信号输出的,而树莓派的引脚不支持模拟信号直接读取(需要增加数模转换芯片),所以先用Arduino来实验。

Arduino 代码

根据电路图, 把Arduino和传感器连接起来:

  1. Sharp pin 1 (V-LED) => 5V 串联1个150欧姆的电阻(最好在电阻一侧和GND之间再串联一个220uf的电容
  2. Sharp pin 2 (LED-GND) => GND
  3. Sharp pin 3 (LED) => Arduino PIN 2 (开关LED)
  4. Sharp pin 4 (S-GND) => GND
  5. Sharp pin 5 (Vo) => Arduino A0 pin (空气质量数据通过电压模拟信号输出)
  6. Sharp pin 6 (Vcc) => 5V
/*
 Interface to Sharp GP2Y1010AU0F Particle Sensor
 Program by Christopher Nafis 
 Written April 2012
 
 http://www.sparkfun.com/datasheets/Sensors/gp2y1010au_e.pdf
 http://sensorapp.net/?p=479
 
 Sharp pin 1 (V-LED)   => 5V (connected to 150ohm resister)
 Sharp pin 2 (LED-GND) => Arduino GND pin
 Sharp pin 3 (LED)     => Arduino pin 2
 Sharp pin 4 (S-GND)   => Arduino GND pin
 Sharp pin 5 (Vo)      => Arduino A0 pin
 Sharp pin 6 (Vcc)     => 5V
 */
#include <SPI.h>
#include <stdlib.h>

int dustPin=0;
int ledPower=2;
int delayTime=280;
int delayTime2=40;
float offTime=9680;

int dustVal=0;
int i=0;
float ppm=0;
char s[32];
float voltage = 0;
float dustdensity = 0;
float ppmpercf = 0;

void setup(){
  Serial.begin(9600);
  pinMode(ledPower,OUTPUT);

  // give the ethernet module time to boot up:
  delay(1000);

  i=0;
  ppm =0;
}

void loop(){
  i=i+1;
  digitalWrite(ledPower,LOW); // power on the LED
  delayMicroseconds(delayTime);
  dustVal=analogRead(dustPin); // read the dust value
  ppm = ppm+dustVal;
  delayMicroseconds(delayTime2);
  digitalWrite(ledPower,HIGH); // turn the LED off
  delayMicroseconds(offTime);

  voltage = ppm/i*0.0049;
  dustdensity = 0.17*voltage-0.1;
  ppmpercf = (voltage-0.0256)*120000;
  if (ppmpercf < 0)
    ppmpercf = 0;
  if (dustdensity < 0 )
    dustdensity = 0;
  if (dustdensity > 0.5)
    dustdensity = 0.5;
  String dataString = "";
  dataString += dtostrf(voltage, 9, 4, s);
  dataString += ",";
  dataString += dtostrf(dustdensity, 5, 2, s);
  dataString += ",";
  dataString += dtostrf(ppmpercf, 8, 0, s);
  i=0;
  ppm=0;
  Serial.println(dataString);
  delay(1000);
}

把传感器和Ardiuno连接好后,可以连续打印出传感器的输出电压值。输出电压大小和灰尘含量的曲线入下图:

[Read More]

在树莓派上使用Phantomjs自动登录微博

使用过新浪开放平台的朋友都知道用户对小应用(用户数较少的)的授权Token很容易过期,自动续期要求授权过的用户在过期前重新打开授权页。如果你想实现一个自动备份自己微博的App,就不得每天(周)自己去访问授权页(想死的心都有了吧?)。这里介绍一种通过脚本自动登录微博获取最新oAuth token的方法(需要微博登录名和密码),合适自己玩。将脚本部署在树莓派上后,我再也不用每周都去登录一次授权页了,只是收到报警消息后(经常是帐号被冻结了)需要手动处理一下。

Phantomjs

Phantomjs 是一个开源的,没有界面可运行在命令行,跨平台,基于WebKit的全功能浏览器,可以用来做网站自动化测试。从源代码编译比较费时间,可以直接下载二进制版本,树莓派的版本在这里可下载。Phantomjs下载好了后就一个可执行文件,依赖非常少,我很喜欢这种方式。

代码

以下代码使用提供的微博用户名和密码登录,获得Token后还会打开微博首页看帐号是否被冻结了。

var page = require('webpage').create(),
    system = require('system'),
    fs = require('fs'),
    address;

var weibo_userid = system.args[1]
var weibo_passwd = system.args[2]

var startUrl = "https://api.weibo.com/oauth2/authorize?client_id=<your_app_key>&redirect_uri=<your_return_url>/&response_type=token";

var verify_weibo_freeze = false;

page.onResourceReceived = function (res,network) {
    if (res.stage == "end") {
        // console.log("\t<-" + res.url);
        if (res.url.indexOf("authorize?client_id")>0) {
            startUrl = res.url
        } 
        if (res.url.indexOf("?access_token")>0) {
            var pos1 = res.url.indexOf("access_token=")
            var pos2 = res.url.indexOf("&")
            var access_token = res.url.substring(pos1+"access_token=".length, pos2)
            console.log(weibo_userid + " login OK, access_token is: " + access_token)
            verify_weibo_freeze = true
        }
        if (verify_weibo_freeze && res.url != "http://weibo.com/" && res.url.indexOf("http://weibo.com/")>-1) {
            var pos1 = res.url.indexOf("/",8)
            var pos2 = res.url.indexOf("?")
            var weibo_name = res.url.substring(pos1+1,pos2)
            console.log(weibo_name+" status verified OK")
            phantom.exit();
        }
    }
};

page.onLoadFinished = function() {
    if (verify_weibo_freeze) {
        page.open("http://weibo.com/", function() {
            phantom.exit();
        })
    }
};

page.onConsoleMessage = function(msg) {
    console.log(msg);
};

page.open(startUrl, function(status) {
    if ( status === "success" ) {
        page.includeJs("https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function() {
            var offset = page.evaluate(function(a,b) {
                $("#userId").val(a)
                $("#passwd").val(b)
                if ($('.WB_btn_login').hasClass("formbtn_01")) {
                    // console.log("Found button!")
                    return $('.WB_btn_login').offset()
                }
                return undefined
            }, weibo_userid, weibo_passwd);
            page.sendEvent('click', offset.left + 1, offset.top + 1);
        });
    }
})

执行方法

timeout 120 phantomjs weibo_login.js <your_weibo_login_id> <your_weibo_login_password>

[Read More]

在Mac上使用Sublime 3写Go代码

Sublime 是一个相当好用的文本编辑器,界面简洁,功能强大。最近Sublime 3 Beta 出来了, 体验了一下,发现启动速度比之前快了很多。

下载安装

下载地址: http://www.sublimetext.com/3

安装Package Control

Sublime 支持插件来丰富其功能,package control 本身也是一个插件,可以用来管理其他插件,所以我们要先安装Package Control,Sublime 3需要安装Pacakge Control Alpha.

cd "Library/Application Support/Sublime Text 3"
cd Packages/
git clone https://github.com/wbond/sublime_package_control.git "Package Control"
cd "Package Control"
git checkout python3

安装GoSublime

重启Sublime后

  1. 按cmd+shift+p (OS X)或press ctrl+shift+p (Windows, Linux)
  2. 在弹出的输入框中输入 PacInstall 选择 Package Control: Install Package
  3. 在稍后弹出的输入框内输入"Gosublime",选择安装

好了,就这样可以开始写Go代码了。

Tips

  1. 我的Sublime配置

    {
    	"font_face": "Microsoft YaHei",
    	"font_options":
    	[
    	],
    	"font_size": 18.0,
        "line_padding_top": 0
    }
    
  2. 按快捷键Shift + Command + L可以按列编辑

    [Read More]

替换树莓派的U盘

除了SD卡上的存储,树莓派还可以使用U盘来做存储,有时候我们可能需要替换已有的U盘为更大容量的。在Mac上可以采用下面的方法:

  1. 备份已有的U盘,把U盘从树莓派上拔下来插在Mac上,找出U盘对应的盘符(下例为/dev/disk2

    20:51:19 hugozhu-mac-mini ~ $ diskutil list
    /dev/disk0
       #:                       TYPE NAME                    SIZE       IDENTIFIER
       0:      GUID_partition_scheme                        *500.1 GB   disk0
       1:                        EFI                         209.7 MB   disk0s1
       2:                  Apple_HFS Macintosh HD            499.2 GB   disk0s2
       3:                 Apple_Boot Recovery HD             650.0 MB   disk0s3
    /dev/disk2
       #:                       TYPE NAME                    SIZE       IDENTIFIER
       0:     FDisk_partition_scheme                        *2.1 GB     disk2
    

    使用 dd 命令把U盘拷贝到raspberrypi.img

    sudo dd if=/dev/disk2 of=raspberrypi.img conv=notrunc
    
  2. 从Mac上取下旧U盘,把新的U盘插入同一个USB口,注意新U盘容量要大于旧的

    sudo dd of=/dev/disk2 if=raspberrypi.img conv=notrunc
    
  3. 把新U盘插入树莓派,并mount上,用以下命令把U盘的多余空间用起来

    [Read More]

Java并发中正确使用volatile

前几天并发编程群里有同学对volatile的用法提出了疑问,刚好我记得Twitter有关实时搜索的这个PPT对这个问题解释的很清晰并有一个实际的应用场景,于是周末把这个问题摘录了一些和并发相关的内容如下:

并发 - 定义

悲观锁 - Pressimistic locking

  1. 一个线性在执行一个操作时持有对一个资源的独占锁。(互斥)
  2. 一般用在冲突比较可能发生的场景下

乐观锁 - Optimistic locking

  1. 尝试采用原子操作,而不需要持有锁;冲突可被检测,如果发生冲突,具有相应的重试逻辑
  2. 通常用在冲突较少发生的场景下

非阻塞算法 - Non-blocking algorithm

  1. 算法确保对线程间竞争共享资源时候,不会因为互斥而使任一线程的执行无限延迟;

无锁算法 - Lock-free algorithm

  1. 如果系统整个流程的执行是无阻塞的(系统某一部分可能被短暂阻塞),这种非阻塞算法就是无锁的。
  2. 无锁算法比传统的基于锁的算法对系统的开销更小,且更容易在多核多CPU处理器上扩展;
  3. 在实时系统中可以避免锁带来的延迟;
  4. CAS (compare and swap)或LL/SC(load linked/store conditional),以及内存屏障相关的指令经常被用在算法实现中。

无等待算法 - Wait-free algorithm

  1. 如果每个线程的执行都是无阻塞的,这种非阻塞算法就是无等待的(比无锁算法更好)

Java的并发

  1. Java的内存模型并不保证一个线程可以一直以程序执行的顺序看到另一个线程对变量的修改,除非两个线程都跨越了同一个内存屏障。(Safe publication)

Java内存模型

代码顺序规则

  1. 一个线程内的每个动作 happens-before 同一个线程内在代码顺序上在其后的所有动作

volatile变量规则

  1. 对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入

传递性

  1. 如果A happens-before B, B happens-before C,那 A happens-before C

Safe publication案例

class VolatileExample {
    int x = 0;
    volatile int b = 0;

    private void write() {
        x = 5;
        b = 1;
    }

    private void read() {
        int dummy = b;
        while (x!=5) {
        }
    }

    public static void main(String[] args) throws Exception {
        final VolatileExample example = new VolatileExample();
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                example.write();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                example.read();
            }
        });
        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
    }
}

x并不需要定义为volatile, 程序里可以有需要类似x的变量,我们只需要一个volatile变量b来确保线程a能看到线程1对x的修改:

[Read More]