Pagsusuri sa AOP at Android application ng AOP
Aop Analysis Android Application Aop
I. Panimula
Alam ng lahat ang OOP, Program-oriented Programming, object-oriented na programa. Sa artikulong ito ay pag-uusapan natin ang tungkol sa AOP, Aspect-oriented Programming, na nakatuon sa pag-program ng aspeto (mukha). Karaniwan ay nagkakaroon kami ng mga ideya sa programa ng OOP. Ang kakanyahan ng ideyang ito ay upang baguhin ang problema. Ang bawat module ay nakatuon sa sarili nitong mga gawain, ngunit sa totoong mundo, hindi lahat ng mga problema ay maaaring ganap na nahahati sa mga modyul. Tulad ng output ng log, maaaring ito ang pag-andar na kailangan ng bawat module, kaya sa mundo ng OOP, ang ilang mga pagpapaandar ay naka-spaced at naka-embed sa maraming mga module, pagkatapos ang layunin ng AOP ay maipagsama ang mga pagpapaandar na ito, ilagay ang Go to a pinag-isang lugar upang makontrol at pamahalaan. Kaya, bilang buod, ang OOP ay dapat hatiin ang problema sa isang solong module, pagkatapos ang AOP ay upang pamahalaan ang isang tiyak na uri ng mga problema na kinasasangkutan ng maraming mga module.
Pangalawa, pagpapakilala ng AspectJ
2.1 Panimula sa AspetoJ
Ang pinakatanyag na wika ng pag-unlad ng OOP ay ang Java. Kaya para sa AOP, ang ilang mga tagabunsod ay nakabuo ng isang wika upang suportahan ang AOP. Ang pinakatanyag ay ang AspectJ. Ang AspectJ ay ang Aspect ng Java, AOP ng Java, ito ay halos isang Java. Ang eksaktong parehong wika, at ganap na katugma sa Java, bilang karagdagan sa espesyal na wika ng AspecJ, sinusuportahan din ng AspectJ ang katutubong Java, idagdag lamang ang kaukulang mga anotasyon ng AspectJ, kaya mayroong dalawang paraan upang magamit ang AspectJ:
- Ang wika ng AspectJ ay ganap na ginagamit. Ito ay katulad ng wikang Java. Maaari din itong tumawag sa Java class library na arbitrarily sa AspectJ. Ang AspectJ lamang ay may ilang mga keyword ng sarili nitong, ngunit dahil ang file ay .aj, kailangan mo ang plugin ng IDE upang maisagawa ang syntax. Suriin at ipunin.
- Binuo sa purong Java at pagkatapos ay gumagamit ng mga anotasyon ng AspectJ, dinaglat @AspectJ
Hindi alintana kung aling pamamaraan ang ginagamit, ang tool sa pagtitipon ng AspectJ na AJC ay kinakailangang mag-ipon, ngunit karaniwang pipiliin namin ang pangalawa, dahil ang una ay hindi makawala sa suporta ng ajc, at hindi katulad ng java syntax at mga file, mahirap na pag-isahin ang detalye ng pag-coding, at Mayroon ding pangangailangan para sa karagdagang mga gastos sa pag-aaral, kaya higit na gamitin ang syntax ng Java upang tukuyin ang anyo ng mga anotasyon.
Ang AspectJ ay naka-host na ngayon sa Elicpse na proyekto AspectJ address
Ang opisyal na website ay nagbibigay ng link sa pag-download ng aspetoJ.jar. Matapos makumpleto ang pag-download, maaari itong mai-install nang direkta. Pagkatapos ng pag-install, maaari mong makita ang sumusunod na direktoryo.
xueshanshandeMacBook-Pro:aspectj1.9 xueshanshan$ tree bin/ lib/ bin/ ├── aj ├── aj5 ├── ajbrowser ├── ajc └── ajdoc lib/ ├── aspectjrt.jar ├── aspectjtools.jar ├── aspectjweaver.jar └── org.aspectj.matcher.jar
- Ang pakete ng aspetojrt.jar higit sa lahat ay nagbibigay ng ilang mga anotasyon sa runtime, static na pamamaraan, atbp. Karaniwan kailangan naming gamitin ang package na ito kapag gumagamit ng aspetoJ.
- Pangunahing ibinibigay ang Aspectjtools.jar Ajc tagatala Maaari mong i-embed ang aspeto ng kahulugan ng aspeto ng file sa code ng negosyo sa file na java o file ng klase sa oras ng pagsulat. Kadalasan ang bagay na ito ay mai-encapsulate sa iba't ibang mga plugin ng IDE o mga plugin ng automation.
- Pangunahing nagbibigay ang pakete ng aspetojweaver.jar ng ahente ng java para sa paghabi ng aspeto habang naglo-load ang klase, at nagbibigay ng mga pangunahing pamamaraan tulad ng kaugnay na pagproseso ng facet syntax para sa paggamit ng ajc o para sa pag-unlad ng third-party. Ang pakete na ito sa pangkalahatan ay hindi kailangang ipakita. Quote.
Kaya natutunan namin na ang aspetoJ ay may maraming mga pamamaraan sa paghabi:
Mag-ipon sa oras ng pag-ipon
Gamitin ang ajc compiler sa halip na ang javac compiler upang direktang mag-ipon ng mga file ng mapagkukunan sa mga file ng klase at ihabi ang mga ito sa code.
2. Magtipon at maghabi
Gamitin ang tagatala ng ajc upang i-sculpt ang face code sa javac compiled class o jar file.
3. Paghahabi habang naglo-load
Sa halip na gamitin ang tagatala ng ajc, gamitin ang tool na aspetojweaver.jar upang magamit ang java ahente ng proxy ng klase upang ihabi ang aspeto sa code sa panahon ng pag-load ng klase.
2.2 AspectJ syntax
1 、 Sumali sa Point
Ang Sumali sa Point ay tumutukoy sa ilang mga puntos ng pagpapatupad kapag tumatakbo ang programa.
Ang isang function na tawag ay maaaring isang JPoint, tulad ng pag-andar ng Log.e (), ang e ay maaaring maging isang JPoint, at ang pagpapaandar na tumatawag sa e ay maaari ding isaalang-alang bilang isang JPoint, magtakda ng isang variable, o ang isang variable ay maaaring mabasa bilang isang JPoint .
Sa teorya, maraming mga lugar sa isang programa ang maaaring isaalang-alang bilang JPoint, ngunit sa Aspect, ang sumusunod lamang na talahanayan ang maituturing na JPoints:
2. Pointcut (pointcut)
Ipinakilala namin ang JPoint sa itaas, ngunit sa isang klase, tiyak na maraming mga JPoint na nasiyahan ang mga kundisyon. Tiyak na nais naming bigyang pansin ang nais namin, pagkatapos ay ibinibigay ng Pointcut ang pagpapaandar na ito. Ang layunin nito ay upang magbigay ng isang paraan upang makagawa ng kaunlaran. Maaaring pumili ng Sumali sa Point na interesado sila.
Ang pinakakaraniwang pamantayan sa pagpili para sa PointCuts ay:
Tiyak na Sanggunian sa Lagda
Kaugnay na wildcard
Sa itaas ng maraming Pointcuts syntax ay maaaring gumamit ng mga operator tulad ng ‘&&’, ‘||’, ‘!’, At ilang iba pang tukoy na syntax upang mai-filter, partikular:
Ang sumusunod ay tungkol sa paggamit ng Payo, maunawaan ang Payo upang tukuyin ang tukoy na lokasyon ng pag-uugali ng operasyon sa Pointcuts (bago, pagkatapos, parsela), ang mga tukoy na operator ay ang mga sumusunod:
Pangalawa, gumagamit ang Android ng AspectJ
Napaka-kapaki-pakinabang ng AOP, mula sa Spring hanggang Android, saanman, lalo na sa likod, ang Spring ay napaka-maginhawa upang magamit, at napakalakas, ngunit sa Android, ang pagpapatupad ng AspectJ ay na-castrate Ang bersyon, hindi lahat ng mga tampok ay suportado, ngunit para sa pangkalahatan client development, ito ay ganap na sapat.
Pagsasama ng AspectJ sa Android, ang pangunahing ideya ay ang proseso ng pag-package ng hoot Apk, gamit ang tool na AJC na ibinigay ng AspectJ upang maipon ang .class file.
Sa pangkalahatan, kung mai-access mo ang AspectJ mismo, sundin ang mga hakbang sa ibaba.
1. Ipakilala ang aspetojtools plugin sa ilalim ng build.gradle ng direktoryo ng ugat.
buildscript { dependencies { classpath 'org.aspectj:aspectjtools:1.8.10' } }
2, ipinakilala sa build.gradle ng direktoryo ng module ng app
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 -> 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 } } } } dependencies { ... compile 'org.aspectj:aspectjrt:1.8.10' }
Maaari itong makita na ang pagsasama ng AspectJ sa Android ay talagang mas kumplikado, hindi isang isang linya na pagsasaayos, ngunit ang prinsipyo ay talagang upang palitan ang tagatala ng Java sa AJC. Sa kasalukuyan maraming mga plugin na magagamit sa Android Studio sa Github. Ang mga plugin na ito ay maaaring magamit upang madaling maisama ang AspectJ sa Android. Sa katunayan, na-configure lamang nila ang code sa itaas para sa iyo. Malalaman mo
Hugo
AspectJX
gradle-android-aspetoj-plugin
T-MVP
Narito ang chestnut na gumagamit ng AspectJ sa Android, pakinggan ang onCreate na paraan ng Aktibidad, at i-print ang log:
/** * @author xueshanshan * @date 2018/12/1 */ @Aspect public class ActivityAspect { private static final String TAG = ActivityAspect.class.getSimpleName() @Pointcut('execution(* *..BaseActivity.onCreate(..))') The effect is the same as the following sentence @Pointcut('execution(* *..AppCompatActivity+.onCreate(..))') public void logForActivity() {} @Before('logForActivity()') public void log(JoinPoint joinPoint) { Log.e(TAG, 'getKind = ' + joinPoint.getKind()) int length = joinPoint.getArgs().length for (int i = 0 i < length i++) { Object o = joinPoint.getArgs()[i] if (o != null) { Log.e(TAG, 'args[' + i + '] =' + o.toString()) } } Log.e(TAG, 'getSignature = ' + joinPoint.getSignature().toString()) Log.e(TAG, 'getSourceLocation = ' + joinPoint.getSourceLocation().toString()) Log.e(TAG, 'getStaticPart = ' + joinPoint.getStaticPart().toString()) Log.e(TAG, 'getTarget = ' + joinPoint.getTarget().toString()) Log.e(TAG, 'getThis = ' + joinPoint.getThis().toString()) Log.e(TAG, 'toString = ' + joinPoint.toString()) } }
Ang log na nakalimbag pagkatapos na maipatupad ang code sa itaas ay:
12-03 11:24:57.968 10885-10885/com.star.testapplication E/ActivityAspect: after OnCreate getKind = method-execution getSignature = void com.star.testapplication.activitys.BaseActivity.onCreate(Bundle) getSourceLocation = BaseActivity.java:13 getStaticPart = execution(void com.star.testapplication.activitys.BaseActivity.onCreate(Bundle)) getTarget = com.star.testapplication.activitys.MainActivity@cdb6e41 getThis = com.star.testapplication.activitys.MainActivity@cdb6e41 toString = execution(void com.star.testapplication.activitys.BaseActivity.onCreate(Bundle)) 12-03 11:24:58.013 10885-10885/com.star.testapplication E/ActivityAspect: after OnCreate getKind = method-execution getSignature = void com.star.testapplication.activitys.MainActivity.onCreate(Bundle) getSourceLocation = MainActivity.java:51 getStaticPart = execution(void com.star.testapplication.activitys.MainActivity.onCreate(Bundle)) getTarget = com.star.testapplication.activitys.MainActivity@cdb6e41 getThis = com.star.testapplication.activitys.MainActivity@cdb6e41 toString = execution(void com.star.testapplication.activitys.MainActivity.onCreate(Bundle))
Kung nais mong gayahin ang Application.registerActivityLifecycleCallbacks upang makuha ang callback para sa bawat lifecycle ng Aktibidad, kailangan nating makuha ang entry point para sa bawat panahon ng pagdeklara ng Aktibidad, ngunit makaka-engkwentro kami ng dalawang mga problema:
-
Kung mayroong isang magulang na klase ng BaseActivity, kung gayon ang aktibidad ng subclass ay override din ang onCreate na pamamaraan, kung gayon ang mga pamamaraan ng onCrate ng dalawang klase na ito ay habi sa AOP code, kaya magkakaroon ng dalawang beses. Ayon sa log sa itaas, maaari itong tapusin na ang AspectJ ay mahirap. Direktang naharang ang isang subclass at hindi nakakaapekto sa isang pamamaraan ng magulang na klase.
-
Kung ang aktibidad ng subclass ay hindi override ng isang pamamaraan, tulad ng onDestroy na pamamaraan, kung gayon ang onDestroy na pamamaraan ng Aktibidad ay hindi maaaring gamitin bilang isang pointcut. Iyon ay, hindi maharang ng AspectJ ang mga subclass na hindi override sa magulang na klase.
Kaya't kung nais mong gumawa ng isang bagay tulad ng Application.ActivityLifecycleCallbacks, mayroong isang simpleng paraan upang gawin ito: magkaroon ng isang BaseActivity, i-override ang lahat ng mga paraan ng lifecycle, tukuyin ang isang anotasyon, magdagdag ng mga anotasyon sa mga pamamaraang lifecycle na ito, at pagkatapos ay tukuyin ang Pointcut Cut sa tala na ito, narito ang kastanyas na isinulat ko:
/** * Annotation class ActivityLifeCycle * @author xueshanshan * @date 2018/12/3 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ActivityLifeCycle { }
/** * Constant class LifeCycleMethod * @author xueshanshan * @date 2018/12/3 */ public class LifeCycleMethod { public static final String LIFECYCLE_METHOD_ON_CREATE = 'onCreate' public static final String LIFECYCLE_METHOD_ON_RESUME = 'onResume' public static final String LIFECYCLE_METHOD_ON_PAUSE = 'onPause' public static final String LIFECYCLE_METHOD_ON_STOP = 'onStop' public static final String LIFECYCLE_METHOD_ON_DESTROY = 'onDestroy' @Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) @StringDef({LIFECYCLE_METHOD_ON_CREATE, LIFECYCLE_METHOD_ON_RESUME, LIFECYCLE_METHOD_ON_PAUSE, LIFECYCLE_METHOD_ON_STOP, LIFECYCLE_METHOD_ON_DESTROY}) public @interface MethodName { } }
//BaseActivity public abstract class BaseActivity extends AppCompatActivity { protected Context mContext @ActivityLifeCycle() @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState) mContext = this } @ActivityLifeCycle() @Override protected void onResume() { super.onResume() } @ActivityLifeCycle() @Override protected void onPause() { super.onPause() } @ActivityLifeCycle() @Override protected void onStop() { super.onStop() } @ActivityLifeCycle() @Override protected void onDestroy() { super.onDestroy() } }
/** * Cut noodles * @author xueshanshan * @date 2018/12/3 */ @Aspect public class ActivityAnnotationAspect { private static final String TAG = ActivityAnnotationAspect.class.getSimpleName() private boolean foreground = false private boolean paused = true private Handler handler = new Handler() private static final long CHECK_DELAY = 500L private Runnable pauseRunnable = new Runnable() { @Override public void run() { if (!paused) { return } if (foreground) { foreground = false notifyStatusChanged(false) } } } @Pointcut('execution(@com.huli.hulitestapplication.annotations.ActivityLifeCycle * *(..)) && @annotation(activityLifeCycle)') public void activitylifeCycle(ActivityLifeCycle activityLifeCycle) {} @After('activitylifeCycle(activityLifeCycle)') public void afterActivitylifeCycle(JoinPoint joinPoint, ActivityLifeCycle activityLifeCycle) { String methodName = joinPoint.getSignature().getName() Class<?> aClass = joinPoint.getThis().getClass() String className = aClass.getSimpleName() Log.e(TAG, 'after ' + className + '->' + methodName) switch (methodName) { case LifeCycleMethod.LIFECYCLE_METHOD_ON_CREATE: break case LifeCycleMethod.LIFECYCLE_METHOD_ON_RESUME: if (!foreground) { notifyStatusChanged(true) } foreground = true paused = false handler.removeCallbacks(pauseRunnable) break case LifeCycleMethod.LIFECYCLE_METHOD_ON_PAUSE: paused = true handler.removeCallbacks(pauseRunnable) handler.postDelayed(pauseRunnable, CHECK_DELAY) break case LifeCycleMethod.LIFECYCLE_METHOD_ON_STOP: break case LifeCycleMethod.LIFECYCLE_METHOD_ON_DESTROY: break default: break } } private void notifyStatusChanged(boolean foreground) { if (foreground) { Log.d(TAG, 'app become foreground') } else { Log.d(TAG, 'app become background') } } }
Narito ang naka-print na log:
12-03 18:28:48.385 18001-18001/? E/ActivityAnnotationAspect: after MainActivity->onCreate 12-03 18:28:48.435 18001-18001/? E/ActivityAnnotationAspect: after MainActivity->onResume 12-03 18:28:48.435 18001-18001/? D/ActivityAnnotationAspect: app become foreground 12-03 18:28:52.665 18001-18001/? E/ActivityAnnotationAspect: after MainActivity->onPause 12-03 18:28:52.685 18001-18001/? E/ActivityAnnotationAspect: after TestActivity->onCreate 12-03 18:28:52.695 18001-18001/? E/ActivityAnnotationAspect: after TestActivity->onResume 12-03 18:28:53.185 18001-18001/? E/ActivityAnnotationAspect: after MainActivity->onStop 12-03 18:28:56.125 18001-18001/? E/ActivityAnnotationAspect: after TestActivity->onPause 12-03 18:28:56.465 18001-18001/? E/ActivityAnnotationAspect: after TestActivity->onStop 12-03 18:28:56.625 18001-18001/? D/ActivityAnnotationAspect: app become background 12-03 18:29:03.535 18001-18001/? E/ActivityAnnotationAspect: after TestActivity->onResume 12-03 18:29:03.535 18001-18001/? D/ActivityAnnotationAspect: app become foreground
Bilang karagdagan, kung nais mong gumawa ng isang kaganapan na nakakakuha ng pamamahagi ng kaganapan, ngunit hindi ito maipatupad, ang dahilan ay:
- Ang view ng system ay hindi nakabalot sa apk, kaya't hindi ito maaaring i-cut.
- Para sa pasadyang View, kung hindi mo muling susulat ang kaukulang paraan ng pamamahagi ng kaganapan, hindi mo ito maaaring i-cut. Siyempre, maaari mo ring gamitin ang pamamaraang anotasyon sa itaas, ngunit ang pamamaraang ito ay maaari lamang magamit para sa pasadyang View.
Maaari ring ipatupad ng AOP ang paglilibing ng mga kaganapan sa pag-click, atbp .:
@After('execution(* android.view.View.OnClickListener.onClick(android.view.View))') public void callOnClick(JoinPoint joinPoint) { Log.d(TAG, 'callOnClick') }
Sa itaas ay ang cut-in ng onClick na kaganapan ng view. Ang iba pang mga kaganapan ay magkatulad. Napakadali na gawin ang operasyon ng paglilibing. Kung hindi ito inilibing, ipamamahagi ito sa bawat klase, na ginagawang napaka-bloated ang code.
Ang AOP sa ganitong paraan sa android ay upang ipagsama ang code sa oras ng pag-ipon, mahahanap namin ang sagot sa direktoryo ng build:
Pangatlo, buod
Bagaman sa ilang mga kaso, maaaring may iba pang mga problema sa pamamaraang ito, tulad ng pagtingin sa pamamahagi ng kaganapan ay hindi maaaring gupitin, ngunit marami pa ring hindi naunlad na mga eksena upang mag-isip at galugarin, at ito ay isang ideya, kung sa ilang mga punto kailangan mo lamang upang bigyang pansin ang isang aspeto ng problema, at hindi nais na baguhin ang code ng maraming, kung gayon ang ideya ng AOP cut-in ay napaka-angkop, ngunit ang susi ay upang makahanap ng angkop na paraan ng pag-cut-in at angkop na aplikasyon mga senaryo Ang paggamit ng mga anotasyon ay kasalukuyang isang paghahambing. Mahusay na paraan upang i-cut in.
Ang ganitong pag-iisip ng AOP ay malawakang ginagamit sa Android, tulad ng pag-print ng log, aplikasyon ng pahintulot, atbp Bilang karagdagan, ang buong punong panlibing ng kasalukuyang patakaran na ginamit sa proyekto ay napagtanto din sa ganitong paraan.