parms = session.getParms();
340 | if (parms.get("username") == null) {
341 | msg += "\n";
342 | } else {
343 | msg += "Hello, " + parms.get("username") + "!
";
344 | }
345 | return newFixedLengthResponse(msg + "\n");
346 | }
347 | }
348 | ```
349 |
350 | 运行App,浏览器打开[http://localhost:8080/ ](http://localhost:8080/)即可看到效果。
351 |
352 | 这是官方文档的简单示例,我们基于Android稍微改造下,通过Service来启动HttpServer。
353 |
354 | 创建一个服务类AndroidHttpServer 继承 NanoHTTPD,完整的AndroidHttpServer 如下:
355 | ```
356 | public class AndroidHttpServer extends NanoHTTPD {
357 |
358 | private static final String TAG = "AndroidHttpServer";
359 |
360 | //定义一个默认的端口号
361 | private static final int DEFAULT_PORT = 8088;
362 |
363 | //Prometheus用于获取数据
364 | private CollectorRegistry registry;
365 |
366 | //ByteArrayOutputStream
367 | private final LocalByteArray response = new LocalByteArray();
368 | private static class LocalByteArray extends ThreadLocal {
369 | protected ByteArrayOutputStream initialValue()
370 | {
371 | return new ByteArrayOutputStream(1 << 20);
372 | }
373 | }
374 |
375 | public AndroidHttpServer() {
376 | this(DEFAULT_PORT);
377 | }
378 |
379 | public AndroidHttpServer(int port) {
380 | super(port);
381 | registry = CollectorRegistry.defaultRegistry;
382 | }
383 |
384 | public AndroidHttpServer(String hostname, int port) {
385 | super(hostname, port);
386 | registry = CollectorRegistry.defaultRegistry;
387 | }
388 |
389 | @Override
390 | public Response serve(IHTTPSession session) {
391 | //获取浏览器输入的Uri
392 | String uri = session.getUri();
393 | //获取session的Method
394 | Method method = session.getMethod();
395 | Log.i(TAG, "method = " + method + " uri= " + uri);
396 | //这里需要判断下Uri是否符合要求,比如浏览器输入http://localhost:8088/metrics符合,其他都不合符。
397 | if(uri.startsWith("/metrics")){
398 | //本地输出流
399 | ByteArrayOutputStream response = this.response.get();
400 | if(response == null){
401 | return newFixedLengthResponse("response is null ");
402 | }
403 | //每次使用前要reset
404 | response.reset();
405 | //创建一个Writer
406 | OutputStreamWriter osw = new OutputStreamWriter(response);
407 | try {
408 | TextFormat.write004(osw, registry.filteredMetricFamilySamples(parseQuery(uri)));
409 | osw.flush();
410 | osw.close();
411 | response.flush();
412 | response.close();
413 | } catch (IOException e) {
414 | e.printStackTrace();
415 | }
416 | return responseMetrics(session, response.toByteArray());
417 | }
418 | return response404(uri);
419 | }
420 |
421 | /**
422 | * 是否压缩
423 | * @param session IHTTPSession
424 | * @return boolean
425 | */
426 | protected static boolean shouldUseCompression(IHTTPSession session) {
427 | String encodingHeaders = session.getHeaders().get("Accept-Encoding");
428 | if (encodingHeaders == null) return false;
429 |
430 | String[] encodings = encodingHeaders.split(",");
431 | for (String encoding : encodings) {
432 | if (encoding.trim().toLowerCase().equals("gzip")) {
433 | return true;
434 | }
435 | }
436 | return false;
437 | }
438 |
439 |
440 | /**
441 | * 解析uri
442 | * @param query String
443 | * @return Set
444 | * @throws IOException
445 | */
446 | protected static Set parseQuery(String query) throws IOException {
447 | Set names = new HashSet<>();
448 | if (query != null) {
449 | String[] pairs = query.split("&");
450 | for (String pair : pairs) {
451 | int idx = pair.indexOf("=");
452 | if (idx != -1 && URLDecoder.decode(pair.substring(0, idx), "UTF-8").equals("name[]")) {
453 | names.add(URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
454 | }
455 | }
456 | }
457 | return names;
458 | }
459 |
460 | /**
461 | * 访问/metrics,返回对应的Response
462 | * @param session IHTTPSession
463 | * @param bytes byte[]
464 | * @return Response
465 | */
466 | private Response responseMetrics(IHTTPSession session,byte[] bytes) {
467 | //调用newFixedLengthResponse,生成一个Response
468 | Response response = newFixedLengthResponse(Response.Status.OK, NanoHTTPD.MIME_PLAINTEXT,new ByteArrayInputStream(bytes), bytes.length);
469 | //Header添加Content-Type:"text/plain; version=0.0.4; charset=utf-8"
470 | response.addHeader("Content-Type", TextFormat.CONTENT_TYPE_004);
471 | if (shouldUseCompression(session)) {
472 | //Header添加Content-Encoding:"gzip"
473 | response.addHeader("Content-Encoding", "gzip");
474 | try {
475 | ByteArrayOutputStream out = new ByteArrayOutputStream();
476 | GZIPOutputStream gzip = new GZIPOutputStream(out);
477 | gzip.write(bytes);
478 | gzip.close();
479 | response.setData(new ByteArrayInputStream(out.toByteArray()));
480 | out.close();
481 | } catch (IOException e) {
482 | e.printStackTrace();
483 | }
484 | }else{
485 | response.addHeader("Content-Length", String.valueOf(bytes.length));
486 | }
487 | return response;
488 | }
489 |
490 | /**
491 | * 访问无效页面,返回404
492 | * @param url 没有定义的url
493 | * @return Response
494 | */
495 | private Response response404(String url) {
496 | //构造一个简单的Html 404页面
497 | StringBuilder builder = new StringBuilder();
498 | builder.append("");
499 | builder.append("Sorry,Can't Found Uri:" );
500 | builder.append(url );
501 | builder.append(" !");
502 | builder.append("\n");
503 | //调用newFixedLengthResponse返回一个固定长度的Response
504 | return newFixedLengthResponse(builder.toString());
505 | }
506 | }
507 |
508 |
509 | ```
510 |
511 | 创建AndroidHttpService来启动AndroidHttpServer。
512 |
513 | ```
514 | public class AndroidHttpService extends Service {
515 | public AndroidHttpService() {
516 | }
517 |
518 | @Override
519 | public int onStartCommand(Intent intent, int flags, int startId) {
520 | try {
521 | //注册prometheus的采集器
522 | new MemoryUsageCollector(getApplicationContext()).register();
523 | //启动AndroidHttpServer
524 | new AndroidHttpServer().start();
525 | } catch (IOException e) {
526 | e.printStackTrace();
527 | }
528 | return super.onStartCommand(intent, flags, startId);
529 | }
530 |
531 | @Override
532 | public IBinder onBind(Intent intent) {
533 | // TODO: Return the communication channel to the service.
534 | throw new UnsupportedOperationException("Not yet implemented");
535 | }
536 | }】
537 | ```
538 |
539 | 浏览器访问:[http://设备IP:8088/metrics](http://192.168.0.2:8088/metrics),返回类似数据,即代表成功。
540 |
541 | ```
542 | # HELP MemoryUsage Android Performance Monitors
543 | # TYPE MemoryUsage gauge
544 | MemoryUsage{MemoryUsage="MemoryUsage",} 64.57366943359375
545 | ```
546 |
547 |
548 | ## PushGateWay
549 |
550 |
551 | PushGateWay用于Push方案,需要Prometheus先配置PushGateWay。
552 |
553 | [Pushgateway](https://github.com/prometheus/pushgateway) 是 Prometheus 生态中一个重要工具,使用它的原因主要是:
554 |
555 | - Prometheus 采用 pull 模式,可能由于不在一个子网或者防火墙原因,导致 Prometheus 无法直接拉取各个 target 数据。
556 | - 在监控业务数据的时候,需要将不同数据汇总, 由 Prometheus 统一收集。
557 |
558 | 由于以上原因,不得不使用 pushgateway,但在使用之前,有必要了解一下它的一些弊端:
559 |
560 | - 将多个节点数据汇总到 pushgateway, 如果 pushgateway 挂了,受影响比多个 target 大。
561 | - Prometheus 拉取状态 `up` 只针对 pushgateway, 无法做到对每个节点有效。
562 | - Pushgateway 可以持久化推送给它的所有监控数据。
563 |
564 | 因此,即使你的监控已经下线,Prometheus 还会拉取到旧的监控数据,需要手动清理 pushgateway 不要的数据。
565 |
566 |
567 | ### Pushgateway 安装和使用
568 | 中文教程:[ttps://songjiayang.gitbooks.io/prometheus/content/pushgateway/how.html](https://songjiayang.gitbooks.io/prometheus/content/pushgateway/how.html)
github:[https://github.com/prometheus/pushgateway](https://github.com/prometheus/pushgateway)
569 |
570 | prometheus.yml中Pushgateway配置如下
571 | ```
572 | - job_name: 'pushgateway'
573 | honor_labels: true
574 | static_configs:
575 | - targets: ['填入IP:9091']
576 | labels:
577 | instance: pushgateway
578 | ```
579 |
580 | 配置成功即可通过Push的方式,往Pushgateway上传数据。
581 |
582 |
583 | ### SDK的使用
584 | Gradle添加以下依赖
585 | ```
586 | implementation 'io.prometheus:simpleclient_pushgateway:0.8.0'
587 | ```
588 |
589 | 定义IPushGateWay接口,PushGateWayImpl实现这个接口。
IPushGateWay类
590 | ```
591 | public interface IPushGateWay {
592 | String getInstanceKey();
593 | String getInstanceValue();
594 | String getJobName();
595 | void push();
596 | }
597 | ```
598 |
599 |
600 | PushGateWayImpl类
601 | ```
602 | public class PushGateWayImpl implements IPushGateWay{
603 |
604 | private Context context;
605 |
606 | //根据需要改成可配置的
607 | private static final String DEFAULT_PUSH_GATEWAY_SERVER_IP = "IP:9091";//pushgateway的ip
608 |
609 | private static String getIpAddressString() {
610 | try {
611 | for (Enumeration enNetI = NetworkInterface
612 | .getNetworkInterfaces(); enNetI.hasMoreElements(); ) {
613 | NetworkInterface netI = enNetI.nextElement();
614 | for (Enumeration enumIpAddress = netI
615 | .getInetAddresses(); enumIpAddress.hasMoreElements(); ) {
616 | InetAddress inetAddress = enumIpAddress.nextElement();
617 | if (inetAddress instanceof Inet4Address && !inetAddress.isLoopbackAddress()) {
618 | return inetAddress.getHostAddress();
619 | }
620 | }
621 | }
622 | } catch (SocketException e) {
623 | e.printStackTrace();
624 | }
625 | return "0.0.0.0";
626 | }
627 |
628 | public PushGateWayImpl(Context context) {
629 | this.context = context;
630 | }
631 |
632 | @Override
633 | public String getInstanceKey() {
634 | return "instance";
635 | }
636 |
637 | @Override
638 | public String getInstanceValue() {
639 | return getIpAddressString();
640 | }
641 |
642 | @Override
643 | public String getJobName() {
644 | return "AndroidJob";
645 | }
646 |
647 |
648 | @Override
649 | public void push() {
650 | try{
651 | //CollectorRegistry
652 | CollectorRegistry registry = new CollectorRegistry();
653 | //Gauge Of MemoryUsage
654 | Gauge gaugeMemoryUsage = Gauge.build("MemoryUsage", "Android Performance Monitors").create();
655 | gaugeMemoryUsage.set(CollectorUtil.getMemoryUsed(context));
656 | gaugeMemoryUsage.register(registry);
657 | //Push To Gateway
658 | PushGateway pg = new PushGateway(DEFAULT_PUSH_GATEWAY_SERVER_IP);
659 | Map groupingKey = new HashMap<>();
660 | groupingKey.put(getInstanceKey(), getInstanceValue());
661 | pg.pushAdd(registry, getJobName(), groupingKey);
662 | } catch (Exception e){
663 | e.printStackTrace();
664 | }
665 | }
666 |
667 | }
668 | ```
669 |
670 | AndroidHttpService完整代码
671 |
672 | ```
673 | public class AndroidHttpService extends Service {
674 |
675 | private static final String TAG = "AndroidHttpService";
676 |
677 | private ScheduledExecutorService mScheduledExecutorService = Executors.newScheduledThreadPool(1);
678 | public AndroidHttpService() {
679 | }
680 |
681 | @Override
682 | public int onStartCommand(Intent intent, int flags, int startId) {
683 | //Pull方案的NanoHTTPD实现,在设备内置一个HTTPServer供外部访问
684 | try {
685 | //注册prometheus的采集器
686 | new MemoryUsageCollector(getApplicationContext()).register();
687 | //启动AndroidHttpServer
688 | new AndroidHttpServer().start();
689 | } catch (IOException e) {
690 | e.printStackTrace();
691 | }
692 | //Push方案的PushGateWay实现,使用scheduleWithFixedDelay定时上传数据到PushGateWay的接口
693 | final PushGateWayImpl pushGateWayImp = new PushGateWayImpl(getApplicationContext());
694 | mScheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
695 | @Override
696 | public void run() {
697 | Log.i(TAG, "❤❤❤❤❤❤❤❤❤❤❤❤❤❤");
698 | pushGateWayImp.push();
699 | }
700 | }, 0, 10, TimeUnit.SECONDS);
701 | return super.onStartCommand(intent, flags, startId);
702 | }
703 |
704 | @Override
705 | public IBinder onBind(Intent intent) {
706 | // TODO: Return the communication channel to the service.
707 | throw new UnsupportedOperationException("Not yet implemented");
708 | }
709 | }
710 | ```
711 |
712 |
713 | 在Service里通过ScheduledExecutorService创建一个定时任务,定时上传监控数据到PushGateWay。浏览器访问:PushGateWay的IP:9091,如出现自己设备IP的instance,即代表数据上传成功,之后就可以通过Prometheus直接查询相应的数据指标,并配置到Grafana面板里。
714 |
715 | #
716 |
717 | # 小结
718 | Matrix可用于收集Android设备的相关性能指标,是一个数据采集工具。根据实际需求可以替换或自己编写相应的采集工具,获取到数据后通过Pull或者Push的方式与Prometheus对接,最终在Grafana面板上看到实时采集的数据,达到监控和数据可视化的目的。
719 |
720 | 当然还可以接入报警通知等业务,进一步的了解和使用可以参考:
721 |
722 | [Prometheus 实战](https://songjiayang.gitbooks.io/prometheus/content/concepts/metric-types.html)
[Prometheus 教程](https://github.com/yunlzheng/prometheus-book)
723 |
724 | 非常感谢作者大大们!
725 |
726 |
727 | # 参考资料
728 | 本文的撰写参考了以下资料,同样非常感谢!点击即可跳转到原文。
729 |
730 | [什么是应用性能管理](https://support.huaweicloud.com/productdesc-apm/apm_06_0006.html)
[应用性能管理(APM, Application Performance Management)](https://www.cnblogs.com/polaris16/p/8886319.html)
[Android开发:移动端APM性能监控](https://www.jianshu.com/p/905081fb873b)
[Android APM 系列一(原理篇)](https://www.imooc.com/article/33354?block_id=tuijian_wz)
[Android 微信APM工具 Matrix使用](https://www.jianshu.com/p/0ff8646871f9)
731 |
732 |
733 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 29
5 | buildToolsVersion "29.0.3"
6 | defaultConfig {
7 | applicationId "com.xiangang.apm"
8 | minSdkVersion 23
9 | targetSdkVersion 29
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation fileTree(dir: 'libs', include: ['*.jar'])
24 | implementation 'androidx.appcompat:appcompat:1.1.0'
25 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
26 | testImplementation 'junit:junit:4.12'
27 | androidTestImplementation 'androidx.test.ext:junit:1.1.1'
28 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
29 | implementation 'org.nanohttpd:nanohttpd:2.3.1'
30 | /*implementation 'io.prometheus:simpleclient:0.8.0'*/
31 | implementation 'io.prometheus:simpleclient_pushgateway:0.8.0'
32 | }
33 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/xiangang/apm/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.xiangang.apm;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.test.platform.app.InstrumentationRegistry;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 |
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | import static org.junit.Assert.*;
12 |
13 | /**
14 | * Instrumented test, which will execute on an Android device.
15 | *
16 | * @see Testing documentation
17 | */
18 | @RunWith(AndroidJUnit4.class)
19 | public class ExampleInstrumentedTest {
20 | @Test
21 | public void useAppContext() {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
24 |
25 | assertEquals("com.xiangang.apm", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xiangang/apm/AndroidHttpServer.java:
--------------------------------------------------------------------------------
1 | package com.xiangang.apm;
2 |
3 | import android.util.Log;
4 |
5 | import java.io.ByteArrayInputStream;
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.IOException;
8 | import java.io.OutputStreamWriter;
9 | import java.net.URLDecoder;
10 | import java.util.HashSet;
11 | import java.util.Set;
12 | import java.util.zip.GZIPOutputStream;
13 |
14 | import fi.iki.elonen.NanoHTTPD;
15 | import io.prometheus.client.CollectorRegistry;
16 | import io.prometheus.client.exporter.common.TextFormat;
17 |
18 | /**
19 | * ================================================
20 | * Created by xiangang on 2020/3/15 17:45
21 | * Contact me
22 | * Follow me
23 | * ================================================
24 | */
25 | public class AndroidHttpServer extends NanoHTTPD {
26 |
27 | private static final String TAG = "AndroidHttpServer";
28 |
29 | private static final int DEFAULT_PORT = 8088;
30 |
31 | //Prometheus用于注册Collector
32 | private CollectorRegistry registry;
33 |
34 | //ByteArrayOutputStream
35 | private final LocalByteArray response = new LocalByteArray();
36 | private static class LocalByteArray extends ThreadLocal {
37 | protected ByteArrayOutputStream initialValue()
38 | {
39 | return new ByteArrayOutputStream(1 << 20);
40 | }
41 | }
42 |
43 | public AndroidHttpServer() {
44 | this(DEFAULT_PORT);
45 | }
46 |
47 | public AndroidHttpServer(int port) {
48 | super(port);
49 | registry = CollectorRegistry.defaultRegistry;
50 | }
51 |
52 | public AndroidHttpServer(String hostname, int port) {
53 | super(hostname, port);
54 | registry = CollectorRegistry.defaultRegistry;
55 | }
56 |
57 | @Override
58 | public Response serve(IHTTPSession session) {
59 | //获取浏览器输入的Uri
60 | String uri = session.getUri();
61 | //获取session的Method
62 | Method method = session.getMethod();
63 | Log.i(TAG, "method = " + method + " uri= " + uri);
64 | //这里需要判断下Uri是否符合要求,比如浏览器输入http://localhost:8088/metrics符合,其他都不合符。
65 | if(uri.startsWith("/metrics")){
66 | //本地输出流
67 | ByteArrayOutputStream response = this.response.get();
68 | if(response == null){
69 | return newFixedLengthResponse("response is null ");
70 | }
71 | //每次使用前要reset
72 | response.reset();
73 | //创建一个Writer
74 | OutputStreamWriter osw = new OutputStreamWriter(response);
75 | try {
76 | TextFormat.write004(osw, registry.filteredMetricFamilySamples(parseQuery(uri)));
77 | osw.flush();
78 | osw.close();
79 | response.flush();
80 | response.close();
81 | } catch (IOException e) {
82 | e.printStackTrace();
83 | }
84 | return responseMetrics(session, response.toByteArray());
85 | }
86 | return response404(uri);
87 | }
88 |
89 | /**
90 | * 是否压缩
91 | * @param session IHTTPSession
92 | * @return boolean
93 | */
94 | protected static boolean shouldUseCompression(IHTTPSession session) {
95 | String encodingHeaders = session.getHeaders().get("Accept-Encoding");
96 | if (encodingHeaders == null) return false;
97 |
98 | String[] encodings = encodingHeaders.split(",");
99 | for (String encoding : encodings) {
100 | if (encoding.trim().toLowerCase().equals("gzip")) {
101 | return true;
102 | }
103 | }
104 | return false;
105 | }
106 |
107 |
108 | /**
109 | * 解析uri
110 | * @param query String
111 | * @return Set
112 | * @throws IOException
113 | */
114 | protected static Set parseQuery(String query) throws IOException {
115 | Set names = new HashSet<>();
116 | if (query != null) {
117 | String[] pairs = query.split("&");
118 | for (String pair : pairs) {
119 | int idx = pair.indexOf("=");
120 | if (idx != -1 && URLDecoder.decode(pair.substring(0, idx), "UTF-8").equals("name[]")) {
121 | names.add(URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
122 | }
123 | }
124 | }
125 | return names;
126 | }
127 |
128 | /**
129 | * 访问/metrics,返回对应的Response
130 | * @param session IHTTPSession
131 | * @param bytes byte[]
132 | * @return Response
133 | */
134 | private Response responseMetrics(IHTTPSession session,byte[] bytes) {
135 | //调用newFixedLengthResponse,生成一个Response
136 | Response response = newFixedLengthResponse(Response.Status.OK, NanoHTTPD.MIME_PLAINTEXT,new ByteArrayInputStream(bytes), bytes.length);
137 | //Header添加Content-Type:"text/plain; version=0.0.4; charset=utf-8"
138 | response.addHeader("Content-Type", TextFormat.CONTENT_TYPE_004);
139 | if (shouldUseCompression(session)) {
140 | //Header添加Content-Encoding:"gzip"
141 | response.addHeader("Content-Encoding", "gzip");
142 | try {
143 | ByteArrayOutputStream out = new ByteArrayOutputStream();
144 | GZIPOutputStream gzip = new GZIPOutputStream(out);
145 | gzip.write(bytes);
146 | gzip.close();
147 | response.setData(new ByteArrayInputStream(out.toByteArray()));
148 | out.close();
149 | } catch (IOException e) {
150 | e.printStackTrace();
151 | }
152 | }else{
153 | response.addHeader("Content-Length", String.valueOf(bytes.length));
154 | }
155 | return response;
156 | }
157 |
158 | /**
159 | * 访问无效页面,返回404
160 | * @param url 没有定义的url
161 | * @return Response
162 | */
163 | private Response response404(String url) {
164 | //构造一个简单的Html 404页面
165 | StringBuilder builder = new StringBuilder();
166 | builder.append("");
167 | builder.append("Sorry,Can't Found Uri:" );
168 | builder.append(url );
169 | builder.append(" !");
170 | builder.append("\n");
171 | //调用newFixedLengthResponse返回一个固定长度的Response
172 | return newFixedLengthResponse(builder.toString());
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xiangang/apm/AndroidHttpService.java:
--------------------------------------------------------------------------------
1 | package com.xiangang.apm;
2 |
3 | import android.app.Service;
4 | import android.content.Intent;
5 | import android.os.IBinder;
6 | import android.util.Log;
7 |
8 | import java.io.IOException;
9 | import java.util.concurrent.Executors;
10 | import java.util.concurrent.ScheduledExecutorService;
11 | import java.util.concurrent.TimeUnit;
12 |
13 | public class AndroidHttpService extends Service {
14 |
15 | private static final String TAG = "AndroidHttpService";
16 |
17 | private ScheduledExecutorService mScheduledExecutorService = Executors.newScheduledThreadPool(1);
18 | public AndroidHttpService() {
19 | }
20 |
21 | @Override
22 | public int onStartCommand(Intent intent, int flags, int startId) {
23 | //Pull方案的NanoHTTPD实现,在设备内置一个HTTPServer供外部访问
24 | try {
25 | //注册prometheus的采集器
26 | new MemoryUsageCollector(getApplicationContext()).register();
27 | //启动AndroidHttpServer
28 | new AndroidHttpServer().start();
29 | } catch (IOException e) {
30 | e.printStackTrace();
31 | }
32 | //Push方案的PushGateWay实现,使用scheduleWithFixedDelay定时上传数据到PushGateWay的接口
33 | final PushGateWayImpl pushGateWayImp = new PushGateWayImpl(getApplicationContext());
34 | mScheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
35 | @Override
36 | public void run() {
37 | Log.i(TAG, "❤❤❤❤❤❤❤❤❤❤❤❤❤❤");
38 | pushGateWayImp.push();
39 | }
40 | }, 0, 10, TimeUnit.SECONDS);
41 | return super.onStartCommand(intent, flags, startId);
42 | }
43 |
44 | @Override
45 | public IBinder onBind(Intent intent) {
46 | // TODO: Return the communication channel to the service.
47 | throw new UnsupportedOperationException("Not yet implemented");
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xiangang/apm/CollectorUtil.java:
--------------------------------------------------------------------------------
1 | package com.xiangang.apm;
2 |
3 | import android.app.ActivityManager;
4 | import android.content.Context;
5 |
6 | import java.io.BufferedReader;
7 | import java.io.FileReader;
8 | import java.io.IOException;
9 |
10 | /**
11 | * ================================================
12 | * Created by xiangang on 2020/3/15 18:38
13 | * Contact me
14 | * Follow me
15 | * ================================================
16 | */
17 | public class CollectorUtil {
18 | /**
19 | * 计算已使用内存的百分比,并返回。
20 | * @return
21 | */
22 | public static float getMemoryUsed(Context context) {
23 | String dir = "/proc/meminfo";
24 | try {
25 | FileReader fr = new FileReader(dir);
26 | BufferedReader br = new BufferedReader(fr, 2048);
27 | String memoryLine = br.readLine();
28 | String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));
29 | br.close();
30 | long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\\D+", ""));
31 | long availableSize = getAvailableMemory(context) / 1024;
32 | float percent = ((totalMemorySize - availableSize) / (float) totalMemorySize * 100);
33 | return percent;
34 | } catch (IOException e) {
35 | e.printStackTrace();
36 | }
37 | return 0;
38 | }
39 |
40 | /**
41 | * 获取当前可用内存,返回数据以字节为单位。
42 | * @return
43 | */
44 | private static long getAvailableMemory(Context context) {
45 | ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
46 | ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
47 | am.getMemoryInfo(mi);
48 | return mi.availMem;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xiangang/apm/ICollector.java:
--------------------------------------------------------------------------------
1 | package com.xiangang.apm;
2 |
3 | /**
4 | * ================================================
5 | * Created by xiangang on 2020/3/15 18:34
6 | * Contact me
7 | * Follow me
8 | * ================================================
9 | */
10 | public interface ICollector {
11 |
12 | String getMetricName();
13 | String getHelp();
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xiangang/apm/IPushGateWay.java:
--------------------------------------------------------------------------------
1 | package com.xiangang.apm;
2 |
3 | /**
4 | * ================================================
5 | * Created by xiangang on 2020/3/15 20:35
6 | * Contact me
7 | * Follow me
8 | * ================================================
9 | */
10 | public interface IPushGateWay {
11 | String getInstanceKey();
12 | String getInstanceValue();
13 | String getJobName();
14 | void push();
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xiangang/apm/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.xiangang.apm;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 |
6 | import androidx.appcompat.app.AppCompatActivity;
7 |
8 | public class MainActivity extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_main);
14 | //启动AndroidHttpService
15 | startService(new Intent(this,AndroidHttpService.class));
16 | }
17 |
18 | @Override
19 | protected void onDestroy() {
20 | super.onDestroy();
21 | stopService(new Intent(this,AndroidHttpService.class));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xiangang/apm/MemoryUsageCollector.java:
--------------------------------------------------------------------------------
1 | package com.xiangang.apm;
2 |
3 | import android.content.Context;
4 |
5 | import java.util.ArrayList;
6 | import java.util.Arrays;
7 | import java.util.List;
8 |
9 | import io.prometheus.client.Collector;
10 |
11 | /**
12 | * ================================================
13 | * Created by xiangang on 2020/3/15 18:35
14 | * Contact me
15 | * Follow me
16 | * ================================================
17 | */
18 | public class MemoryUsageCollector extends Collector implements ICollector {
19 |
20 | private Context context;
21 |
22 | public MemoryUsageCollector(Context context) {
23 | this.context = context;
24 | }
25 |
26 | @Override
27 | public String getMetricName() {
28 | return "MemoryUsage";
29 | }
30 |
31 | @Override
32 | public String getHelp() {
33 | return "Android Performance Monitors";
34 | }
35 |
36 | @Override
37 | public List collect() {
38 | List mfsList = new ArrayList<>();
39 | String metricName = getMetricName();
40 | MetricFamilySamples.Sample sample = new MetricFamilySamples.Sample(metricName, Arrays.asList(metricName), Arrays.asList(metricName), CollectorUtil.getMemoryUsed(context));
41 | MetricFamilySamples mfs = new MetricFamilySamples(metricName, Type.GAUGE, getHelp(), Arrays.asList(sample));
42 | mfsList.add(mfs);
43 | return mfsList;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xiangang/apm/PushGateWayImpl.java:
--------------------------------------------------------------------------------
1 | package com.xiangang.apm;
2 |
3 | import android.content.Context;
4 |
5 | import java.net.Inet4Address;
6 | import java.net.InetAddress;
7 | import java.net.NetworkInterface;
8 | import java.net.SocketException;
9 | import java.util.Enumeration;
10 | import java.util.HashMap;
11 | import java.util.Map;
12 |
13 | import io.prometheus.client.CollectorRegistry;
14 | import io.prometheus.client.Gauge;
15 | import io.prometheus.client.exporter.PushGateway;
16 |
17 | /**
18 | * ================================================
19 | * Created by xiangang on 2020/3/15 20:36
20 | * Contact me
21 | * Follow me
22 | * ================================================
23 | */
24 | public class PushGateWayImpl implements IPushGateWay{
25 |
26 | private Context context;
27 |
28 | //根据需要改成可配置的
29 | private static final String DEFAULT_PUSH_GATEWAY_SERVER_IP = "192.168.3.13:9091";//pushgateway的ip
30 |
31 | private static String getIpAddressString() {
32 | try {
33 | for (Enumeration enNetI = NetworkInterface
34 | .getNetworkInterfaces(); enNetI.hasMoreElements(); ) {
35 | NetworkInterface netI = enNetI.nextElement();
36 | for (Enumeration enumIpAddress = netI
37 | .getInetAddresses(); enumIpAddress.hasMoreElements(); ) {
38 | InetAddress inetAddress = enumIpAddress.nextElement();
39 | if (inetAddress instanceof Inet4Address && !inetAddress.isLoopbackAddress()) {
40 | return inetAddress.getHostAddress();
41 | }
42 | }
43 | }
44 | } catch (SocketException e) {
45 | e.printStackTrace();
46 | }
47 | return "0.0.0.0";
48 | }
49 |
50 | public PushGateWayImpl(Context context) {
51 | this.context = context;
52 | }
53 |
54 | @Override
55 | public String getInstanceKey() {
56 | return "instance";
57 | }
58 |
59 | @Override
60 | public String getInstanceValue() {
61 | return getIpAddressString();
62 | }
63 |
64 | @Override
65 | public String getJobName() {
66 | return "AndroidJob";
67 | }
68 |
69 |
70 | @Override
71 | public void push() {
72 | try{
73 | //CollectorRegistry
74 | CollectorRegistry registry = new CollectorRegistry();
75 | //Gauge Of MemoryUsage
76 | Gauge gaugeMemoryUsage = Gauge.build("MemoryUsage", "Android Performance Monitors").create();
77 | gaugeMemoryUsage.set(CollectorUtil.getMemoryUsed(context));
78 | gaugeMemoryUsage.register(registry);
79 | //Push To Gateway
80 | PushGateway pg = new PushGateway(DEFAULT_PUSH_GATEWAY_SERVER_IP);
81 | Map groupingKey = new HashMap<>();
82 | groupingKey.put(getInstanceKey(), getInstanceValue());
83 | pg.pushAdd(registry, getJobName(), groupingKey);
84 | } catch (Exception e){
85 | e.printStackTrace();
86 | }
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangang/AndroidAPMSample/0e05683182245feb4ec6b6b94e17682f07078165/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangang/AndroidAPMSample/0e05683182245feb4ec6b6b94e17682f07078165/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangang/AndroidAPMSample/0e05683182245feb4ec6b6b94e17682f07078165/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangang/AndroidAPMSample/0e05683182245feb4ec6b6b94e17682f07078165/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangang/AndroidAPMSample/0e05683182245feb4ec6b6b94e17682f07078165/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangang/AndroidAPMSample/0e05683182245feb4ec6b6b94e17682f07078165/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangang/AndroidAPMSample/0e05683182245feb4ec6b6b94e17682f07078165/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangang/AndroidAPMSample/0e05683182245feb4ec6b6b94e17682f07078165/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangang/AndroidAPMSample/0e05683182245feb4ec6b6b94e17682f07078165/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangang/AndroidAPMSample/0e05683182245feb4ec6b6b94e17682f07078165/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Apm
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/xiangang/apm/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.xiangang.apm;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 |
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.5.3'
11 |
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 |
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 |
21 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiangang/AndroidAPMSample/0e05683182245feb4ec6b6b94e17682f07078165/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Mar 15 17:30:17 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name='Apm'
3 |
--------------------------------------------------------------------------------