[{"content":"App工程是用Android Studio来开发，使用gradle编译，需求是App工程要放在Android源码中编译集成进系统。为了保留App的工程结构不变，需要在Android.mk中配置才能正常编译。以下各行配置，均在App工程根目录下的Android.mk文件中。\nAndroid.mk 设置当前路径 LOCAL_PATH := $(call my-dir) 清理变量定义 include $(CLEAR_VARS) 编译标志 指该模块在什么编译版本下编译，有以下几种取值\n#eng：在eng版本 #test：在test版本 #user：在user版本 #optional：所有版本都编译 LOCAL_MODULE_TAGS := optional 指定当前源码文件 调用all-java-files-under方法，意思是当前目录下的所有java文件\nLOCAL_SRC_FILES := $(call all-java-files-under) 指定当前资源文件 #在Android Studio的默认工程结构中，资源文件夹位于app/src/main/res中， #所以要在这里指定资源目录，如果不指定，资源文件默认位于当前目录 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/src/main/res 指定AndroidManifest.xml的位置 #默认位置是与Android.mk文件同一目录下，但在Android Studio的默认工程结构中， #AndroidManifest.xml位于app/src/main下， #所以要在这里指定AndroidManifest.xml位置 LOCAL_MANIFEST_FILE := app/src/main/AndroidManifest.xml 如果项目需要引用jar包（步骤1） #冒号后面的名字是自己定义的，定义在这里：引用jar包（步骤2） LOCAL_STATIC_JAVA_LIBRARIES := MultiScreenSupportV4 如果项目需要support-v7包引用 #v7包由系统编译提供，冒号后的名字，是v7包下Android.mk中定义的模块名 LOCAL_JAVA_LIBRARIES := android-support-v7-appcompat #还需要在v7源码中，指定资源文件夹 LOCAL_RESOURCE_DIR += platform/android/frameworks/support/v7/appcompat/res LOCAL_AAPT_FLAGS += \\ --auto-add-overlay \\ --extra-packages android.support.constraint:android.support.v7.appcompat 设置包名 最后会生成以此为名的apk文件\nLOCAL_PACKAGE_NAME := MultiScreenDemo 签名 LOCAL_CERTIFICATE := platform 混淆 LOCAL_PROGUARD_ENABLED := disabled 如果项目需要引用so文件（步骤1） LOCAL_REQUIRED_MODULES := libimagepipeline LOCAL_JNI_SHARED_LIBRARIES := libimagepipeline 编译生成apk保存地址 LOCAL_MODULE_PATH := $(ANDROID_PRODUCT_OUT)/system/app 编译成apk include $(BUILD_PACKAGE) 引用jar包（步骤2） include $(CLEAR_VARS) LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := MultiScreenSupportV4:/app/libmm/android-support-v4.jar include $(BUILD_MULTI_PREBUILT) 引用so文件（步骤2） include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_MODULE_SUFFIX := .so LOCAL_MODULE := libimagepipeline LOCAL_MODULE_CLASS := SHARED_LIBRARIES LOCAL_SRC_FILES_arm := app/libmm/armeabi/libimagepipeline.so LOCAL_MODULE_TARGET_ARCHS:= arm LOCAL_MULTILIB := both include $(BUILD_PREBUILT) 会有啥问题？ gradle项目依赖 在Android Studio中创建的工程，由于使用了gradle来管理依赖，尤其是项目依赖了各种第三方库，当使用Android.mk来编译此项目时，需要手动在mk文件中引用这些第三方库，实际上就是依赖这些第三方库的jar包、源码、so文件等等。\n系统编译后，在指定目录下没有生成apk 如果需要新增加一个apk，编译到系统镜像中，在apk目录下mm命令可以正常编译并输出apk，但系统make时，却没有输出apk，首先要确认LOCAL_MODULE_TAGS字段的值是否是当前系统编译的类型。\n还有一个原因是在系统有个包配置文件build/target/product/generic_no_telephony.mk配置了编译系统时包括的所有模块，在这里加上自己的App的mk文件的LOCAL_PACKAGE_NAME的值即可。\n","permalink":"https://xzxiao.com/build-android-studio-project-with-android-mk/","summary":"\u003cp\u003eApp工程是用Android Studio来开发，使用gradle编译，需求是App工程要放在Android源码中编译集成进系统。为了保留App的工程结构不变，需要在Android.mk中配置才能正常编译。以下各行配置，均在App工程根目录下的Android.mk文件中。\u003c/p\u003e","title":"在Android源码环境下使用Android Make编译App"},{"content":"为什么要迁移到Hexo WordPress的存在，让我们很容易的搭建出属于自己的功能完善的博客系统，并且提供丰富的插件、主题。当然还存在一些问题例如：\n运行时需要PHP与MySQL，个人小站服务器性能孱弱，内存小，这几个占用资源相对较多。让我印象深刻的是Docker中部署MySQL容器，启动后，剩余内存从500MB到不足100MB，很是吓人。 自从开始用Markdown写文章，就爽的停不下来，WordPress需要第三方插件来支持Markdown，效果一般，对表格支持有问题。 最重要的一点是维护成本，WordPress的功能强大，导致维护成本较高，比如备份和迁移，一直用BackWPup来做备份，当恢复导入sql时，会遇到诸如数据库不同版本支持的编码不同，从而导入失败，要手动改sql自动编码格式。解了一个问题还有一系列各种问题，鼓捣鼓捣几个小时就过去了，不优雅。 希望能有这样的方案，用Markdown来编辑内容，用Git来做备份和发布，本地编辑完内容后，一条命令，自动部署到服务器并发布。哪怕服务器挂掉，我的内容仓库在本地或云端始终有备份，只需重新发布即可。 内容与展示的耦合 后来就找到了Hexo，当然还有一些类似的博客系统。看完Hexo官网介绍，心中出现一种声音：就是你了。\nHexo的搭建比较顺利，中间也遇到一些问题，我把问题都总结在本文最后一节。其实不论WordPress也好，Hexo也好，博客最重要的是内容，我们使用的各种博客系统，可以看做是一种内容的展示方式、形式。今天我迁移到了Hexo，也许过一阵子，就有其他的框架更能满足我的需求，可能会再次迁移，由于不同框架的结构不同，设计理念也不同，就会带来额外的迁移维护成本。\n我的设想是，对于内容，自己定义一套存储维护方法：每篇文章使用Markdown写作，图片等资源放入同目录下资源文件夹。因为Markdown是纯文本，可以很方便的使用Git来做版本控制或云端备份。设计一个adapter，用来生成与目标框架匹配的内容。如此，在内容上，我只需要维护自己的这套内容管理方法，如果要发布到WordPress，我就写一个adapter程序来适配WordPress的发布方法；如果要发布到Hexo，就写一个adapter程序适配Hexo的发布方法。通过这个适配器模式，来实现内容维护与展示形式的解耦。\n当本地编辑完成一篇文章，Git提交，并推送到远程仓库，可以设置多个远程仓库，其中一个就放在网站服务器上，服务器Git通过Git Hooks检测到有新文章推送来时，触发Adapter转换程序，开始一系列转换，完成后发布给博客框架。\n解耦方法 本地内容结构 Hexo中source目录结构 搭建Git服务端以及Git Hooks设置 针对Hexo的Adapter 本地内容结构：\narticles/ |---artical1 | |---artical1.md | |---src/ | |---pic1.jpg | |---pic2.jpg |\t|---artical2/ |---artical2.md |---src/ |---pic1.jpg |---pic2.jpg 在artical1.md中，若需要插入图片，可直接引用![](src/pic1.png)，本地编辑过程中即可预览图片。\nHexo中source目录结构：\nsource/_posts/ |---artical1.md |---artical2.md |---artical1/ | |---src/ | |---pic1.jpg | |---pic2.jpg | |---artical2/ |---src/ |---pic1.jpg |---pic2.jpg 由于Hexo的目录机制，在生成public后，首页的图片地址生成有误，需要安装hexo-asset-image插件，并且在_config.yml中设置post_asset_folder: true， 这样来安排Hexo中的目录结构，md中引用图片的路径不需要修改。\n在服务器端，搭建Git仓库，用于接收文章push：\ngit init --bare post.git 就会在当前目录下生成post.git文件夹，使用--bare参数来初始化Git，是创建一个空仓库，这个空仓库没有工作区，在此目录下只能用来接收pull推送，不能对仓库进行pull、checkout、commit等操作。\n在本地，可以通过push来推送提交：\ngit remote add origin2 user@domain.com:/path/to/post.git git push origin2 master user是用来登录服务器的用户名，domain.com是服务器域名，也可用IP代替。\n因为我们要在服务器端监听push动作来触发Adapter程序，所以在post.git/hooks/下，创建post-update文件，实际上post-update可以是个shell脚本，Git会在push后在hooks文件夹下自动根据文件名称去执行。\n我并不擅长写shell，于是在post-update里执行了一段Python脚本，这个exe.py脚本就是Adapter。\n#!/bin/bash unset $(git rev-parse --local-env-vars) export NVM_DIR=\u0026#34;/usr/local/nvm\u0026#34; [ -s \u0026#34;$NVM_DIR/nvm.sh\u0026#34; ] \u0026amp;\u0026amp; \\. \u0026#34;$NVM_DIR/nvm.sh\u0026#34; # This loads nvm python hooks/exe.py 实际上只有最后一行才是执行exe.py，其他几行的用处放在最后面问题总结里面。\n这里Adapter的作用是将我们自定义的内容结构，转换成不同博客框架的文件结构，下面附上针对我自己的内容维护结构转Hexo框架所需文件结构的的Adapter：\n#!/usr/bin/env python # -*- coding: utf-8 -*- import os import subprocess import shutil __author__ = \u0026#34;xzxiao\u0026#34; # 1.清理hexo/source/_post # 2.clone post.git 到hexo/git/pull/文件夹下 # 3.复制pull根目录下文章文件夹到_posts下 # 4.遍历子目录，把_posts文件夹中的所有md文件移动到_posts下 # 5.hexo clean # 6.hexo g HEXO_PATH = \u0026#34;/path/to/hexo\u0026#34; SOURCE_PATH = HEXO_PATH + \u0026#34;/source\u0026#34; POST_PATH = SOURCE_PATH + \u0026#34;/_posts\u0026#34; SRC_PATH = \u0026#34;/src\u0026#34; GIT_DIR = \u0026#34;/path/to/post.git\u0026#34; GIT_PULL_DIR = \u0026#34;/path/to/pull\u0026#34; def clean_post(): print \u0026#34;clean_post: \u0026#34; path = POST_PATH + os.sep + \u0026#34;*\u0026#34; print path subprocess.Popen(\u0026#34;rm -rf \u0026#34; + path, shell=True, executable=\u0026#39;/bin/bash\u0026#39;).wait() def clone(): print \u0026#34;clone:\u0026#34; if os.path.exists(GIT_PULL_DIR + \u0026#34;/.git\u0026#34;): print \u0026#34;.git exist, pulling\u0026#34; subprocess.Popen(\u0026#34;git pull\u0026#34;, shell=True, executable=\u0026#39;/bin/bash\u0026#39;, cwd=GIT_PULL_DIR).wait() else: print \u0026#34;.git not exist, cloning\u0026#34; subprocess.Popen(\u0026#34;git clone \u0026#34; + GIT_DIR + \u0026#34; ./\u0026#34;, shell=True, executable=\u0026#39;/bin/bash\u0026#39;, cwd=GIT_PULL_DIR).wait() def cp_post(): print \u0026#34;cp_post:\u0026#34; for root, dirs, files in os.walk(GIT_PULL_DIR): if root != GIT_PULL_DIR: break for dir_name in dirs: src = os.path.join(root, dir_name) if not src.__contains__(\u0026#34;hook\u0026#34;) and not src.__contains__(\u0026#34;.git\u0026#34;): print \u0026#34;cp \u0026#34; + src + \u0026#34; to \u0026#34; + os.path.join(POST_PATH, dir_name) shutil.copytree(src, os.path.join(POST_PATH, dir_name)) def mv_md(): print \u0026#34;mv_md:\u0026#34; for root, dirs, files in os.walk(POST_PATH): for file_name in files: if file_name.endswith(\u0026#34;.md\u0026#34;): print \u0026#34;mv \u0026#34; + os.path.join(root, file_name) + \u0026#34; to \u0026#34; + POST_PATH shutil.move(os.path.join(root, file_name), POST_PATH) def clean(): print \u0026#34;clean:\u0026#34; subprocess.Popen(\u0026#34;hexo clean\u0026#34;, shell=True, executable=\u0026#39;/bin/bash\u0026#39;, cwd=HEXO_PATH).wait() def g(): print \u0026#34;g:\u0026#34; subprocess.Popen(\u0026#34;hexo g\u0026#34;, shell=True, executable=\u0026#39;/bin/bash\u0026#39;, cwd=HEXO_PATH).wait() if __name__ == \u0026#34;__main__\u0026#34;: clean_post() clone() cp_post() mv_md() clean() g() 开头的注释说明了主要做的6个步骤，当服务器接收到新文章的push后，就会执行此脚本，最终完成Hexo的重新发布。\n我没有用Hexo自带的服务器程序，而是用的Nginx，配置规则后直接跳转到Hexo生成的public目录，其实Hexo就是一个把Markdown文档按照一定规则和主题样式，转换成静态html的框架。\n问题总结 在Git Hooks中执行git pull命令，会出现Not a git repository: '.'错误，当排除脚本里的目录问题后，问题由于hooks在运行的过程中，GIT_DIR和GIT_WORK_TREE环境变量会被设置，导致git pull的目录并不是你指定的那一个，所以需要在git pull之前，执行unset $(git rev-parse --local-env-vars)，重设环境变量。\n脚本中执行hexo命令提示command not found。在当前用户的.bashrc中，nvm会自动添加NVM_DIR变量，用户登录进shell环境，就会自动加载nvm环境变量，如果在hooks中命令需要用到nvm，执行前需要手动设置环境变量\nexport NVM_DIR=\u0026#34;/usr/local/nvm\u0026#34; [ -s \u0026#34;$NVM_DIR/nvm.sh\u0026#34; ] \u0026amp;\u0026amp; \\. \u0026#34;$NVM_DIR/nvm.sh\u0026#34; Python的subprocess.Popen方法，用于替代os.popen()，可以在Python中执行shell命令，此方法参数cwd用于指定当前工作目录，executable用于指定shell，若要等到一条命令执行完后再执行下一个命令，可以在方法最后调用.wait()，阻塞。\nGit Hooks下的post-update文件要给执行权限\nnvm要全局安装，如果只安装给了root，切换到其他用户是找不到nvm的。\n","permalink":"https://xzxiao.com/move-to-hexo/","summary":"\u003ch2 id=\"为什么要迁移到hexo\"\u003e为什么要迁移到Hexo\u003c/h2\u003e\n\u003cp\u003eWordPress的存在，让我们很容易的搭建出属于自己的功能完善的博客系统，并且提供丰富的插件、主题。当然还存在一些问题例如：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e运行时需要PHP与MySQL，个人小站服务器性能孱弱，内存小，这几个占用资源相对较多。让我印象深刻的是Docker中部署MySQL容器，启动后，剩余内存从500MB到不足100MB，很是吓人。\u003c/li\u003e\n\u003cli\u003e自从开始用Markdown写文章，就爽的停不下来，WordPress需要第三方插件来支持Markdown，效果一般，对表格支持有问题。\u003c/li\u003e\n\u003cli\u003e最重要的一点是维护成本，WordPress的功能强大，导致维护成本较高，比如备份和迁移，一直用BackWPup来做备份，当恢复导入sql时，会遇到诸如数据库不同版本支持的编码不同，从而导入失败，要手动改sql自动编码格式。解了一个问题还有一系列各种问题，鼓捣鼓捣几个小时就过去了，不优雅。\u003c/li\u003e\n\u003cli\u003e希望能有这样的方案，用Markdown来编辑内容，用Git来做备份和发布，本地编辑完内容后，一条命令，自动部署到服务器并发布。哪怕服务器挂掉，我的内容仓库在本地或云端始终有备份，只需重新发布即可。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"内容与展示的耦合\"\u003e内容与展示的耦合\u003c/h2\u003e\n\u003cp\u003e后来就找到了Hexo，当然还有一些类似的博客系统。看完Hexo官网介绍，心中出现一种声音：\u003ccode\u003e就是你了\u003c/code\u003e。\u003c/p\u003e","title":"从WordPress迁移到Hexo小记"},{"content":"背景介绍 听倦了每天早晨的起床铃声，从醒来的那一刻就想拥有不同的开始。就像高中时早餐伴随着英语听力，把从起床到出门的时间充分利用起来。因此利用手中的树莓派和小音箱，每天定时播放喜欢的内容。\n方案概述 树莓派是一个运行着Linux系统的微型计算机主板，手中的是一代B型，2个USB接口、3.5mm音频输出以及GPIO接口。\n在实际测试中发现，树莓派自带的3.5mm音频输出功率太小，虽然可以推动耳机，但外接到音箱上，即使把音箱声音开到最大，声音依旧很小，所以声音输出采用了PCM 2704USB声卡，测试后满足需求。\n使用系统自带的定时任务，控制音箱的开闭，播放音频。蓝牙音箱，有外接音源输入接口，音箱的开关是个微动按钮，按下开启，再按下关闭。如果一段时间没有播放，会自动关机，所以要能够让树莓派控制音箱的开闭，需要对音箱的开关做一些改动。通过树莓派的GPIO口来控制音箱开关。\n准备工具：\n树莓派、洞洞板、音箱、电烙铁、继电器、焊锡、杜邦线、排针等。\n实现过程 控制音箱 首先对音箱进行改造。拆开音箱，观察电源按钮，是个微动开关。\n三个引脚，一个脚是公共地端，另两个是开关的输入端。用万用表测量两个输入引脚之间的电压，约4v，是供电锂电池的电压。推测当按下电源键，处于关闭状态的音箱检测到这两端连通，则开机。处于开启状态的音箱检测到这两端连通，则关闭。所以树莓派控制音箱开闭的关键，就是控制这两个开关输入端的连接与断开。\n清楚原理后，用导线将这两端引出到音箱外面。\n通过树莓派的GPIO口来控制继电器，继而控制这两端的开闭，模拟手动按下电源键。\n继电器电路设计如下，2脚和5脚连接树莓派GPIO口（不区分正负极），用于控制继电器。1脚和3脚连接音箱的引线（不区分正负极），其通断被继电器控制。继电器原理图如下。\n不型号继电器有不同的开关电压值，我选择的是3v继电器。当2脚和5脚两端加上3v电压，继电器通电，继电器内电磁感应吸引簧片，被控端导通，1脚与3脚导通，模拟按键按下。当2脚和5脚两端无电压差，继电器断开，1脚与3脚断开，模拟按键松开。\n树莓派GPIO口如下图所示。\n为了能够驱动继电器，选择了13和14脚（对应表中物理引脚/BOARD引脚一栏，下同），当13脚置为高电平时，13脚14脚两端电压与继电器两端电压同为3.3v，此时继电器导通。当13脚置为低电平时，继电器两端电压0v，此时继电器断开。完成一次模拟电源键按下导通，松开断开的过程。\n树莓派使用python来控制GPIO口，安装库RPi.GPIO，命令：\nwget https://pypi.python.org/packages/source/R/RPi.GPIO/RPi.GPIO-0.6.2.tar.gz tar -zxf RPi.GPIO-0.6.2.tar.gz cd RPi.GPIO-0.6.2 python setup.py install 控制程序：\n#!/usr/bin/env python import RPi.GPIO as GPIO import time import os #GPIO 初始化 GPIO.setwarnings(False) GPIO.setmode(GPIO.BOARD)#使用板载引脚序号 GPIO.setup(13,GPIO.OUT)#设置13脚为输出模式 #13脚输出持续1秒的高电平，控制继电器闭合触电闭合 GPIO.output(13,GPIO.HIGH) time.sleep(1) GPIO.output(13,GPIO.LOW) #执行mplayer命令播放mp3文件，播放完毕前程序会阻塞于此 os.system(\u0026#39;mplayer ~/Music/*.mp3\u0026#39;) #播放完毕后关闭音箱 GPIO.output(13,GPIO.HIGH) time.sleep(1) GPIO.output(13,GPIO.LOW) 树莓派声音输出 由于树莓派自带的3.5mm音频输出推力不够，因此使用PCM 2704 USB 声卡输出音频。声卡通过USB与树莓派连接，免驱动，可直接识别，声卡的输出端有光纤，3.5mm和同轴，用一根3.5mm-3.5mm音频线连接声卡与音箱。命令alpay -l来查看当前声卡设备信息。\n图上可以看到USB声卡是card 1，修改~/.asoundrc文件，把默认的声音输出设置为PCM 2704声卡，如果无该文件则新建，内容如下：\npcm.!default { type hw card 1 } ctl.!default { type hw card 1 } card 1为USB声卡在本机识别到的序号。设置好后，查看默认声卡：\nalsamixer 上图左上角显示默认声卡为USB DAC，设置成功。\n到此，所有的连线均已完成，总连接图如下。\n播放内容： 连接好后，使用mplayer播放器测试音箱是否正常播放。mplayer是Linux下常用的媒体播放器，安装方法：\nsudo apt-get install mplayer 将MP3文件复制到树莓派中，播放命令：\nmplayer music.mp3 播放过程中，/控制音量减，*控制音量加，左右方向键快退快进10秒，q或ESC退出播放。\n树莓派定时任务 为了每天早晨定时开启音箱并播放声音，需要让树莓派定时执行python脚本。可以用系统的定时任务来实现。新增调度任务：\n编辑文件/etc/crontab，配置格式字段：\n|Minute|Hour|Day|Month|Dayofweek|user|Command| |-| |分钟|小时|天|月|天每星期|用户|命令|\n字段解释：\nMinute：每个小时的第几分钟执行该任务 Hour：每天的第几个小时执行该任务 Day：每月的第几天执行该任务 Month：每年的第几个月执行该任务 User：执行命令的用户 DayOfWeek：每周的第几天执行该任务 Command：指定要执行的程序 在这些字段里，除了Command是每次都必须指定的字段以外，其它字段皆为可选 对于不指定的字段，要用“*”来填补其位置，字段之间空格隔开。\n比如我要在每天早晨7点执行python脚本，则/etc/crontab文件可以这样配置：\n0 7 * * * pi ~/Projects/SmartHome/main/goodMorning.py 效果与总结 每天早晨定时播放选定的内容，播放完后关闭音箱。完成了当初的设想，但还有很多改进点，例如不仅能够播放本地媒体，还可以播放网络在线内容。连线比较混乱，裸露电线在外面，导致外观看起来不一体。 最后要注意使用定时任务时，树莓派的系统时间是否与当地时间相同，树莓派联网后，默认会同步系统时间与网络时间一致。配置的时区是否正确（中国为+8时区）。\n","permalink":"https://xzxiao.com/raspberry-auto-play-sound/","summary":"\u003ch2 id=\"背景介绍\"\u003e背景介绍\u003c/h2\u003e\n\u003cp\u003e听倦了每天早晨的起床铃声，从醒来的那一刻就想拥有不同的开始。就像高中时早餐伴随着英语听力，把从起床到出门的时间充分利用起来。因此利用手中的树莓派和小音箱，每天定时播放喜欢的内容。\u003c/p\u003e","title":"使用树莓派外接音箱实现自动播放音频"}]