注解及aop小结
主要包括三方面的知识
- 自定义注解
- aop
- gradle插件开发
注解主要是了解如何使用元注解定义注解,如何解析注解
aop的概述及AspectJ的基本用法
gradle插件开发指的是如何利用gradle自定义plugin在项目中引用
我们将利用以上三点知识,模仿hugo做一个实战项目
我们已经学会了定义自定义gradle插件,但是我们还需要定义一些gradle插件里的属性,就像logger一样定义一个enable属性控制是否输出日志
logger {
enable false
}
像这样logger就是一个自定义的属性,其实是很简单的
// 首先需要定义一个类,定义好属性和方法
class LoogerExtension {
def enable = true
def setMessage(String text) {
System.out.println(text)
}
}
class HugoPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
...
project.extensions.create("logger", LoggerExtension)
}
}
// 然后就可以在gradle中使用了
logger {
enable false 或者 enable = false
setMessage 'disable' 或者 message 'disable'
}
如果要定义多个属性,例如要发布多平台就需要定义多个类似的属性
platforms {
wandoujia {
name = 'xxx'
api_key = 'xxx'
}
xiaomi {
name = 'xxx'
api_key = 'xxx'
}
}
首先需要定义属性类
class PlatformExtension {
def name
def api_key
/*
需要string的构造函数,否则报错:
A problem occurred evaluating project ':aop'.
> Could not create an instance of type org.cmdmac.aopplugin.ListExtention.
> Could not find any public constructor for class org.cmdmac.aopplugin.ListExtention which accepts parameters [java.lang.String].
public PlatformExtension(String n) {
this.name = n
}
}
然后在Plugin中添加
class PlatformPlugin implements Plugin<Project> {
@Override
void apply(Project porject) {
...
def platforms = project.container(PlatformExtension)
project.extensions.platform = platforms
}
}
使用:
platforms {
wandoujia {
name = 'xxx'
api_key = 'xxx'
}
xiaomi {
name = 'xxx'
api_key = 'xxx'
}
}
类似这样:
android{
compileSdkVersion 19
defaultConfig {
applicationId 'xxx'
}
}
首先定义Extension
class AndroidExtension {
def Integer compileSdkVerion
def DefaultConfigExtension defaultConfig
public AndroidExtension (org.gradle.internal.impldep.org.sonatype.maven.polyglot.groovy.builder.factory.ObjectFactory objectFactory){
defaultConfig = objectFactory.newInstance(DefaultConfigExtension)
}
void defaultConfig(Action<? super DefaulConfigExtension> action){
action.execute(defaultConfig)
}
class DefaultConfigExtension {
def String applicationId
}
}
在Plugin中创建extension
...
project.extensions.create("android", AndroidExtension, project.objects)
variants分两种分别是libraryVariants和applicationVariants
final def variants = project.android.libraryVariants
final def variants = project.android.applicationVariants
如果aop的代码在另外一个module中,那么你的app module也需要添加aspectj的相关依赖,并且aop module的依赖和app module的依赖只有variants不同。
创建groovy项目:(如果是idea的话直接创建groovy项目即可)
创建plugin文件并编辑,要实现Plugin
compile gradleApi()
compile localGroovy()
implementation 'com.android.tools.build:gradle:3.1.2'
//aspectj需要到的类
implementation 'org.aspectj:aspectjtools:1.8.5'
注意: 如果出错一定要仔细看log,例如我plugin文件用到AppPlugin和LibraryPlugin,没有自动导包,上传到maven仓库的时候不会报错,apply plugin之后编译的时候会报错,有日志
定义maven仓库的task
apply plugin: "maven"
group = 'com.dou.aopplugin'
version = '1.0.0'
uploadArchives{
repositories{
mavenDeployer{
repository(url:uri("../repo"))
}
}
}
项目中引用,注意添加 mavenLocal
project build.gradle下:
buildscript{
repositories{
...
mavenLocal() // 本地maven需要这行,非本地要删除
maven{
url "/home/dou/work/workspace/demos/aopdemo/repo"
}
}
}
dependencies{
...
classpath 'com.dou.aopplugin:aopplugin:1.0.0'
}
通过预编译方式或者运行期动态代理实现程序功能的统一维护的一种技术。
面向对象编程主要用于为同一对象层面的公用行为建模。弱点是不能将公共行为用在多个无关对象之间。而面向切片编程最大的优点就是将公共行为用在不同的对象模块上。
AspectJ是一种语言来支持aop的,但是他也提供了java的方式使用AspectJ。
在module下的build.gradle文件中添加:
buildscript{
repositories {
mavenCentral()
}
dependencies{
classpath 'org.aspectj:aspectjtools:1.8.9'
classpath 'org.aspectj:aspectjweaver:1.8.9'
}
}
... // 省略
dependencies {
...
implementation 'org.aspectj:aspectjrt:1.8.9'
}
... // 省略
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
final def log = project.logger
final def variants = project.android.applicationVariants
variants.all { variant ->
if (!variant.buildType.isDebuggable()) {
log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
return;
}
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["-showWeaveInfo",
"-1.8",
"-inpath", javaCompile.destinationDir.toString(),
"-aspectpath", javaCompile.classpath.asPath,
"-d", javaCompile.destinationDir.toString(),
"-classpath", javaCompile.classpath.asPath,
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
log.debug "ajc args: " + Arrays.toString(args)
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler);
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
log.warn message.message, message.thrown
break;
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
@Aspect
public class TestAspect {
@Pointcut("execution(* com.dou.demo.aop_demo.MainActivity.test(..))")
public void pointcut(){}
@Around("pointcut()")
public void test(ProceedingJoinPoint point) throws Throwable{
System.out.println("@test start");
point.proceed();
System.out.println("@test end");
}
}
定义annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Hugo {
}
使用注解
@Hugo
private void time_consumer() {
for (int i = 0; i < 100; i++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
定义切片
@Aspect
public class HugoAspect {
@Pointcut("execution(@com.dou.demo.aop_demo.Hugo * *(..))")
public void hugo(){}
@Around("hugo()")
public void timer(ProceedingJoinPoint point) throws Throwable{
System.out.println("@hugo start");
MethodSignature signature = (MethodSignature) point.getSignature();
long starttime = System.currentTimeMillis();
point.proceed();
long endtime = System.currentTimeMillis();
System.out.println(signature.getMethod().getName() + " 方法耗时: " + (endtime - starttime) + " ms");
System.out.println("@hugo end");
}
}
重新clean,run
其它一个实例见源码
在上一节的分析中我们已经知道,菜单的显示在页面的oncreateoptionmenu中完成的。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
setupMenu(menu);
return true;
}
这个方法代码很少,知识简单的调用了setupMenu(menu)方法。
private void setupMenu(Menu menu) {
final String menuLanguage = ZLResource.getLanguageOption().getValue();
if (menuLanguage.equals(myMenuLanguage)) {
return;
}
myMenuLanguage = menuLanguage;
menu.clear();
fillMenu(menu, MenuData.topLevelNodes());
synchronized (myPluginActions) {
int index = 0;
for (PluginApi.ActionInfo info : myPluginActions) {
if (info instanceof PluginApi.MenuActionInfo) {
addMenuItem(
menu,
PLUGIN_ACTION_PREFIX + index++,
((PluginApi.MenuActionInfo)info).MenuItemName
);
}
}
}
refresh();
}
这个方法中,MenuData.getTopLevelNodes()即是获取菜单条目数据的方法,fillMenu()是遍历这些条目,并调用addMenuItem()将条目设置给Android optionmenu
private void addMenuItem(Menu menu, String actionId, Integer iconId, String name) {
if (name == null) {
name = ZLResource.resource("menu").getResource(actionId).getValue();
}
final MenuItem menuItem = menu.add(name);
if (iconId != null) {
menuItem.setIcon(iconId);
}
menuItem.setOnMenuItemClickListener(myMenuListener);
myMenuItemMap.put(menuItem, actionId);
}
包括设置name,icon,以及点击事件。其中name是从assert文件中加载的,用的是sax解析。
回到setmenu方法中,填充完menu会遍历myPluginActions集合,然后将myPluginActions的item添加到menu中。
在addMenuItem中,会给每个条目设置点击事件,然后将item和actionid键值对保存到myMenuItemMap中。这个mymenulistener就是提供actionidid,然后由myIdToActionMap找到对应的actionid响应点击事件。
myIdToActionMap添加数据是在ZLApplication.addAction()方法,这个方法的调用有几个地方,1,fbreader.myPluginInfoReceiver.onreceive 2, fbreader.oncreate 3. fbreaderapp的构造方法中。也就是说基本上这些事件都是在应用初始化的时候都定义好了。
在上一节我们分析了菜单条目的加载,主要就是要找到菜单对应的action,我们在fbreader.oncreate方法中发现ActionCode.SHOW_PREFERENCES即为我们要找的阅读设置相关设置
myFBReaderApp.addAction(ActionCode.SHOW_PREFERENCES, new ShowPreferencesAction(this, myFBReaderApp));
在这个action的run方法中我们发现PreferenceActivity即是我们要找的activity文件。
在它的父类的oncreate()方法中
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
// 异常捕捉
Thread.setDefaultUncaughtExceptionHandler(new org.geometerplus.zlibrary.ui.android.library.UncaughtExceptionHandler(this));
// 创建数据库
SQLiteCookieDatabase.init(this);
// 创建preferencescreen
myScreen = getPreferenceManager().createPreferenceScreen(this);
final Intent intent = getIntent();
final Uri data = intent.getData();
final String screenId;
if (Intent.ACTION_VIEW.equals(intent.getAction())
&& data != null && "fbreader-preferences".equals(data.getScheme())) {
screenId = data.getEncodedSchemeSpecificPart();
} else {
screenId = intent.getStringExtra(SCREEN_KEY);
}
Config.Instance().runOnConnect(new Runnable() {
public void run() {
// 抽象方法,子类初始化
init(intent);
// 获取要显示的screen
final Screen screen = myScreenMap.get(screenId);
// 设置preferencescreen
setPreferenceScreen(screen != null ? screen.myScreen : myScreen);
}
});
}
设置页面采用了sharepreference的方式,并且采用过时的方法先获取preferencescreen再设置preferencescreen,这与我们常用的addPreferenceFromResource一样。
init()方法是抽象方法,具体实现就是PreferenceActivity实现的,这个方法中的代码很多,但是主要做的都是创建各种preference,并把preference添加到preferscreen。
我们要在项目中使用fbreader的话,必须要对它做自己的处理,例如修改默认字体,默认背景,翻页方式,默认字体大小等等,上一节我们了解了fbreader是如何设置翻页方式的,我们讲到在init方法中会创建很多的preferencescreen,其中翻页的菜单对应的就是scrollingScreen,
scrollingScreen.addOption(pageTurningOptions.Animation, "animation");
scrollingScreen.addPreference(new AnimationSpeedPreference(
this,
scrollingScreen.Resource,
"animationSpeed",
pageTurningOptions.AnimationSpeed
));
scrollingScreen.addOption(pageTurningOptions.Horizontal, "horizontal");
设置翻页的代码就这么多,一眼就能看出来第一行scrollingScreen.addOption(pageTurningOptions.Animation, “animation”);就是我们要找的翻页方式设置,
public <T extends Enum<T>> Preference addOption(ZLEnumOption<T> option, String key) {
return addPreference(
new ZLEnumPreference<T>(ZLPreferenceActivity.this, option, Resource.getResource(key))
);
}
ZLEnumPreference是ListPreference的子类,用于显示列表菜单,相当于现在的radiugroup,option用于设置初始值,key用于加载对应的选项列表。
我们知道了这个PageTurningOptions对象是用来设置初始值的,然后我们ctrl+鼠标左键看到,这个类是在FBReaderApp中实例化的,
public class PageTurningOptions {
public static enum FingerScrollingType {
byTap, byFlick, byTapAndFlick
}
public final ZLEnumOption<FingerScrollingType> FingerScrolling =
new ZLEnumOption<FingerScrollingType>("Scrolling", "Finger", FingerScrollingType.byTapAndFlick);
// 设置默认翻页无动画
// public final ZLEnumOption<ZLView.Animation> Animation =
// new ZLEnumOption<ZLView.Animation>("Scrolling", "Animation", ZLView.Animation.slide);
public final ZLEnumOption<ZLView.Animation> Animation =
new ZLEnumOption<ZLView.Animation>("Scrolling", "Animation", ZLView.Animation.none);
public final ZLIntegerRangeOption AnimationSpeed =
new ZLIntegerRangeOption("Scrolling", "AnimationSpeed", 1, 10, 7);
public final ZLBooleanOption Horizontal =
new ZLBooleanOption("Scrolling", "Horizontal", true);
public final ZLStringOption TapZoneMap =
new ZLStringOption("Scrolling", "TapZoneMap", "");
}
所以就可以做如上的修改了。
其实打开PageTurningOption所在的目录,这个option目录下还包括ViewOption,ImageOption等,所以很多默认参数修改都可以在这里做。
一些说明文档
所有与系统架构相关的代码都保存在此目录以及include/asm-xx目录中,每种架构对应的子目录都包括下边的目录
除了上述的3个子目录之外,大多数还有boot目录,存放这种硬件平台上启动内核所使用的部分或者全部平台特有代码。
保存了block层的实现代码
保存了内核本身所使用的加密api信息,实现了常用的加密和散列算法,还有一些压缩和crc校验算法
显卡,网卡,scsi适配器,pci总线,usb总线和其它linux支持的外围设备或总线的驱动程序都可以在这里找到。
此目录保存了虚拟文件系统的代码还有各个不同文件系统的代码。linux支持的所有文件系统在fs下都有一个对应的目录。
此目录包括内核中大部分的头文件,
保存内核的初始化代码,包括main.c、创建早期用户空间的代码以及其它初始化代码。
包含了共享内存,信号量和其它形式ipc的代码
内核中最核心的部分,包括了进程的调度,以及进程的创建和销毁等,和系统架构有关的另外一部分代码在arch/../kernel目录下
此目录保存了库代码,这些代码实现了一个标准C库的通用子集,包括字符串和内存操作等函数
包含和与系统架构无关的内存管理的代码,与体系有关的内存管理的代码在arch/../mm目录下
保存了和网络相关的代码,实现了常见的网络协议,如TCP/IP,IPX等
包含了配置内核的脚本文件。如make menuconfig,make xconfig等
包含了linux不同的安全模型的代码,比如linux se
保存了声卡驱动以及其它声音相关的代码
实现了用于打包和压缩的cpio
2018.3.2 【修改】隐藏NavigationBar, 步骤: frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar,java文件line 431设置showNav = false;
2018/3/5 【修改】修改lcd_density默认配置为160,步骤:device/softwinner/lark-eink-perf/lark_eink_perf.mk,修改ro.sf.lcd_density=167 –> ro.sf.lcd_density=160 (删除out/target/product/lark-eink-perf1/system/build.prop之后再编译)
2018/3/5 【修改】预置apk,步骤:
添加lib库。将apk里依赖的so库复制到device/softwinner/lark-common/prebuild/apklib/,修改device/softwinner/lark-common/prebuild/apklib/下的Android.mk文件,
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_PREBUILT_LIBS := libBugly.so libluajit.so liblzma.so
include $(BUILD_MULTI_PREBUILT)
添加apk。将apk包复制到device/softwinner/lark-common/prebuild/apk/,修改device/softwinner/lark-common/prebuild/Android.mk文件,
include $(CLEAR_VARS)
LOCAL_MODULE := MuPDF
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := MuPDF.apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_REQUIRED_MODULES := libmupdf_java
include $(BUILD_PREBUILT)
添加到配置文件。进入lark-eink-perf1这个我们要编译的设备的目录,修改下边的lark-eink-perf1.mk,
PRODUCT_PACKAGES += MuPDF
注意:apk需要是系统签名的apk
设置默认launcher,方法:参考android 开机默认进入指定Launcher@闲庭信步_wwd,1. 修改开机启动的默认launcher。在frameworks/base/services/java/com/android/server/am/ActivityManagerService.java文件下,修改startHomeActivityLocked()方法为如下,
boolean startHomeActivityLocked(int userId) {
if (mHeadless) {
// Added because none of the other calls to ensureBootCompleted seem to fire
// when running headless.
ensureBootCompleted();
return false;
}
if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL
&& mTopAction == null) {
// We are running in factory test mode, but unable to find
// the factory test app, so just sit around displaying the
// error message and don't try to start anything.
return false;
}
Intent intent = getHomeIntent();
ActivityInfo aInfo =
resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
if (aInfo != null) {
//add wwd start
PackageManager pm = mContext.getPackageManager();
Intent newintent = new Intent(Intent.ACTION_MAIN);
newintent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> resolveInfoList = pm.queryIntentActivities(newintent, 0);
//判断带有Intent.CATEGORY_HOME标签的所有activity中如果有你指定的activity则替换
if(resolveInfoList != null){
int size = resolveInfoList.size();
for(int i = 0; i < size; i++){
ResolveInfo rInfo = resolveInfoList.get(i);
if(rInfo.activityInfo.name.equals("com.android.launcher3.Launcher")){
aInfo = rInfo.activityInfo;
}
}
}
//add wwd stop
intent.setComponent(new ComponentName(
aInfo.applicationInfo.packageName, aInfo.name));
// Don't do this if the home app is currently being
// instrumented.
aInfo = new ActivityInfo(aInfo);
aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
ProcessRecord app = getProcessRecordLocked(aInfo.processName,
aInfo.applicationInfo.uid, true);
if (app == null || app.instrumentationClass == null) {
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
//这里启动你已替换过的activity
mStackSupervisor.startHomeActivity(intent, aInfo);
}
}
return true;
}
2.修改按home键强制启动的launcher。在frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java文件中,修改init()为:
public void init(Context context, IWindowManager windowManager,
WindowManagerFuncs windowManagerFuncs) {
......
mHomeIntent = new Intent(Intent.ACTION_MAIN, null);
mHomeIntent.addCategory(Intent.CATEGORY_HOME);
//add wwd start
ComponentName mHomecom = new ComponentName("com.android.launcher3", "com.android.launcher3.Launcher");
mHomeIntent.setComponent(mHomecom);
//add wwd stop
mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
......
}
2018/3/8 修改默认参数,
1. 默认屏幕亮度,在frameworks/base/packages/SettingProvider/res/values/default.xml
<!-- Default screen brightness, from 0 to 255. 102 is 40%. -->
<integer name="def_screen_brightness">102</integer>
2. 默认屏幕关闭时间
在frameworks/base/packages/SettingProvider/res/values/default.xml,
<integer name="def_screen_off_timeout">300000</integer>
3. 默认字体大小,小/普通/大/很大 分别对应 0.85/1/1.15/1.3
在/b288/android/device/softwinner/lark-eink-perf1/lark_eink_perf1.mk
PRODUCT_PROPERTY_OVERRIDES += \
ro.font.scale=1.15 \
2018/3/12,预置文件到sdcard.在device.mk文件中添加
PRODUCT_COPY_FILES += \
${源文件位置}:${内置到的系统位置}
例如
PRODUCT_COPY_FILES += \
device/softwinner/lark-eink-perf1/media/testbooks/xx.txt:system/media/testbook/xx.txt \
2018/5/14, 显示屏显示异常. 烧入程序之后,由于之前设备烧入过linux程序,系统中存在以下两个文件private/default.bin,private/default_4.bin,在系统启动时默认先使用private路径下的default.bin文件,导致设置vcom电压不对,所以这里删除default.bin和default_4.bin.在串口或者adb中使用命令
mount -o remount,rw /private
rm default.bin
rm default_4.bin, reboot
2018/5/15, 更改开机铃声大小, 修改有两种方式. 第一种是直接用软件修改声音文件的大小,这种可以让客户提供合适的声音文件放在/device/softwinner/lark-eink-perf1/media下,命名为boot.wav即可. 第二种方法通过代码修改,在frameworks/base/cmds/bootanimation/BootAnimation.cpp,在playBootMusic方法中,再mp -> prepare之前添加
set volume level
float volume = 0.9;
mp->setVolume(volume, volume);
2018/5/15, 关闭开机启动的log.
echo 0 > /prop/sys/kernel/printk
2018/5/15, 修改开机log,开机log是再kernel层修改的.文件是在/lichee/tools/pack/chips/sun8iw10p1/boot-resource/boot-resource下的bootlogo.bmp,直接同名覆盖即可.