├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── Compile_zh.md ├── LICENSE ├── README.md ├── README_zh.md ├── app ├── .gitignore ├── CMakeLists.txt ├── app-release.apk ├── build.gradle ├── libs │ ├── frpclib-sources.jar │ └── frpclib.aar ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── config.ini │ └── frpc.log │ ├── cpp │ └── native-lib.cpp │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── activity │ │ ├── BaseActivity.java │ │ ├── BaseRecyclerAdapter.java │ │ ├── BaseRecyclerViewActivity.java │ │ ├── BaseRecyclerViewSwipeRefreshActivity.java │ │ ├── BaseViewHolder.java │ │ ├── CustomRecyclerView.java │ │ ├── DividerItemDecoration.java │ │ ├── FileUtils.java │ │ ├── IItemView.java │ │ ├── IRecyclerView.java │ │ ├── IniFile.java │ │ ├── ItemViewCreator.java │ │ ├── LogUtils.java │ │ ├── LoginActivity.java │ │ ├── MyApplication.java │ │ ├── MySuiDaoViewHolder.java │ │ ├── SplashActivity.java │ │ ├── Utils.java │ │ ├── greendao │ │ ├── DBSuiDaoHelper.java │ │ ├── DaoMaster.java │ │ ├── DaoSession.java │ │ ├── HttpUtil.java │ │ ├── SuiDao.java │ │ └── SuiDaoDao.java │ │ ├── knife_net_Activity.java │ │ └── knife_net_list.java │ └── res │ ├── anim │ ├── bottom_slide_enter.xml │ ├── left_slide_exit_anim.xml │ ├── left_slip_enter_anim.xml │ ├── right_slide_enter_anim.xml │ ├── right_slip_exit_anim.xml │ └── top_slide_exit.xml │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── background.jpg │ ├── blue_line.xml │ ├── btn_bg_blue_default.xml │ ├── btn_bg_blue_pressed.xml │ ├── btn_bg_blue_selector.xml │ ├── btn_bg_blue_unenabled.xml │ ├── button_fang.xml │ ├── buttonstyle.xml │ ├── custom_radio_btn.xml │ ├── ic_launcher_background.xml │ ├── ic_must_fill.png │ ├── radiogroup.xml │ ├── start_app.jpg │ ├── text_bg_blue.xml │ ├── text_bg_blue_pressed.xml │ └── text_bg_blue_selector.xml │ ├── layout │ ├── activity_knife_net.xml │ ├── activity_login.xml │ ├── activity_my_suidao.xml │ ├── activity_splash.xml │ ├── item_comm_footer.xml │ └── item_my_suidao.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFM880/frpc-Android/12d3dc849958eae64a42bb640b85048444bd1ee8/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Compile_zh.md: -------------------------------------------------------------------------------- 1 | ## 编译Android-SDK环境指南( 所有操作均在root用户下执行) 2 | 3 | * 安装 go 4 | 5 | ``` 6 | [root@localhost ~]# wget https://www.golangtc.com/static/go/1.10.4/go1.10.4.linux-amd64.tar.gz #下载go1.10.x安装包 7 | [root@localhost ~]# tar -zxvf go1.10.4.linux-amd64.tar.gz #解压 8 | [root@localhost ~]# mv go /usr/local/ #移动运行目录 9 | 10 | [root@localhost ~]# vi /etc/profile #添加系统变量 11 | 12 | export GOROOT=/usr/local/go 13 | export GOBIN=/usr/local/go/bin 14 | export PATH=$PATH:$GOBIN:$JAVA_HOME/bin 15 | export GOPATH=~/go 16 | export ANDROID_HOME=~/go/android-sdk-linux 17 | 18 | 19 | [root@localhost ~]# source /etc/profile #生效 20 | [root@localhost ~]# go version #验证安装是否成功 21 | go version go1.10.4 linux/amd64 22 | ``` 23 | 24 | * 安装 gomobile 安卓开发环境 25 | 26 | ``` 27 | [root@localhost ~]# mkdir -p /usr/local/go/src/golang.org/x #创建golang目录,或存放~/go/src/golang.org/x(有时会被墙所以手动) 28 | [root@localhost ~]# cd /usr/local/go/src/golang.org/x #进入目录 29 | [root@localhost x]# git clone https://github.com/golang/mobile.git #克隆库 30 | [root@localhost ~]# go build golang.org/x/mobile/cmd/gomobile #编译 31 | [root@localhost ~]# go install golang.org/x/mobile/cmd/gomobile #安装 32 | [root@localhost ~]# gomobile init -v 33 | [root@localhost ~]# gomobile version #验证 34 | gomobile version +5704e18 Mon Jan 22 17:02:51 2018 +0000 (android); androidSDK= 35 | 36 | ``` 37 | 38 | * 安装 java jdk 39 | 40 | ``` 41 | Centos 42 | [root@localhost ~]# yum -y install java java-1.8.0-openjdk* #Centos下安装 43 | [root@localhost ~]# java -version #验证 44 | 45 | ubuntu 46 | [root@localhost ~]# apt-get install openjdk-8-jdk #ubuntu下安装 47 | [root@localhost ~]# java -version #验证 48 | ``` 49 | 50 | 51 | * 安装 android ndk 52 | 53 | ``` 54 | [root@localhost ~]# wget http://mirrors.zzu.edu.cn/android/repository/android-ndk-r16b-linux-x86_64.zip #下载源码包 55 | [root@localhost ~]# unzip android-ndk-r16b-linux-x86_64.zip #解压 56 | [root@localhost ~]# gomobile init -ndk ~/android-ndk-r16b #安装 57 | ``` 58 | 59 | * 安装 android sdk 60 | 61 | ``` 62 | [root@localhost ~]# wget http://dl.google.com/android/android-sdk_r24.4.1-linux.tgz #下载 63 | [root@localhost ~]# tar -xvzf android-sdk_r24.4.1-linux.tgz #解压 64 | [root@localhost ~]# mv android-sdk-linux ~/go/ #移动到用户目录下的go目录 65 | [root@localhost ~]# ~/go/android-sdk-linux/tools/android update sdk -u #安装(请耐心等待) 66 | [root@localhost ~]# gomobile version #验证 67 | gomobile version +92f3b9c Wed Oct 10 16:34:05 2018 +0000 (android); androidSDK=/root/go/android-sdk-linux/platforms/android-28 68 | 69 | ``` 70 | 71 | * 创建frp工程 72 | 73 | ``` 74 | [root@localhost ~]# mkdir -p /usr/local/go/src/github.com/fatedier #创建项目目录,或存放~/go/src/github.com/fatedier 75 | [root@localhost ~]# wget https://github.com/fatedier/frp/archive/v0.21.0.tar.gz #拷贝0.13.0 frp源码 76 | [root@localhost ~]# tar -xzvf v0.21.0.tar.gz -C /usr/local/go/src/github.com/fatedier/ #解压至目录 77 | [root@localhost ~]# cd /usr/local/go/src/github.com/fatedier/ #切换目录 78 | [root@localhost fatedier]# mv frp-0.21.0 frp #应环境更名 79 | [root@localhost ~]# cd /usr/local/go/src/github.com/fatedier/frp/cmd/frpc #切换目录 80 | 81 | ``` 82 | 83 | * 编辑源文件(github.com/fatedier/frp/cmd/frpc/sub/root.go) 84 | 85 | ``` 86 | ...... 87 | 88 | 90 - err := runClient(cfgFile) 89 | 90 + err := RunClient(cfgFile) 90 | ...... 91 | 92 | 172 - func runClient(cfgFilePath string) (err error) { 93 | 172 + func RunClient(cfgFilePath string) (err error) { 94 | 95 | ``` 96 | 97 | * 编辑源文件(github.com/fatedier/frp/cmd/frpc/main.go) 98 | 99 | ``` 100 | // Copyright 2016 fatedier, fatedier@gmail.com 101 | // 102 | // Licensed under the Apache License, Version 2.0 (the "License"); 103 | // you may not use this file except in compliance with the License. 104 | // You may obtain a copy of the License at 105 | // 106 | // http://www.apache.org/licenses/LICENSE-2.0 107 | // 108 | // Unless required by applicable law or agreed to in writing, software 109 | // distributed under the License is distributed on an "AS IS" BASIS, 110 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 111 | // See the License for the specific language governing permissions and 112 | // limitations under the License. 113 | 114 | package frpclib 115 | 116 | import ( 117 | "github.com/fatedier/frp/cmd/frpc/sub" 118 | 119 | "github.com/fatedier/golib/crypto" 120 | ) 121 | 122 | func main() { 123 | crypto.DefaultSalt = "frp" 124 | 125 | sub.Execute() 126 | } 127 | 128 | func Run(cfgFilePath string) { 129 | crypto.DefaultSalt = "frp" 130 | 131 | sub.RunClient(cfgFilePath) 132 | } 133 | 134 | ``` 135 | 136 | * 编译库 137 | 138 | ``` 139 | [root@localhost ~]# gomobile bind -target=android github.com/fatedier/frp/cmd/frpc #编译(不报错的情况) 140 | [root@localhost ~]# ls 141 | frpclib.aar frpclib-sources.jar main.bak main.go 142 | ``` 143 | 144 | 145 | 146 | * Android SDK在线更新镜像服务器 147 | 148 | ``` 149 | http://mirrors.zzu.edu.cn/android/repository/ 150 | ``` 151 | 152 | * sdk 153 | 154 | ``` 155 | http://tools.android-studio.org/index.php/sdk 156 | 157 | ``` 158 | 159 | 160 | * ld谷歌 161 | 162 | ``` 163 | http://blog.csdn.net/shuzfan/article/details/52690554 164 | ``` 165 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # frp-Android 2 | 3 | [README](https://github.com/FrpcCluster/frpc-Android/blob/master/README.md) | [中文介绍](https://github.com/FrpcCluster/frpc-Android/blob/master/README_zh.md) 4 | 5 | 6 | # Project Introduction 7 | 8 | frp Source project [https://github.com/fatedier/frp](https://github.com/fatedier/frp) 9 | 10 | frp Help website [https://www.frp.fun/](https://www.frp.fun/) 11 | 12 | QQ group [222670265](https://jq.qq.com/?_wv=1027&k=5kkmkwa) 13 | 14 | 15 | ## Project demo Brief diagram 16 | 17 | frp-Android-02.png frp-Android-03.png frp-Android-04.png 18 | 19 | ## Development instructions 20 | 21 | * [Compilation process](https://github.com/FrpcCluster/frpc-Android/blob/master/Compile_zh.md) 22 | * software[Android studio 3.2](http://www.android-studio.org/) 23 | * Android, .aar Library 24 | Frp uses gombile to implement a go code compiled into android and ios platform can directly call the sdk class library 25 | 26 | ## Android SDK usage 27 | The sdk form provided in the Android system is a class library file with a .aar suffix. When developing, you only need to import the arr class library file into the android project. 28 | 29 | * Import package 30 | 31 | ```java 32 | import frpclib.Frpclib; 33 | ``` 34 | 35 | ## Project logic 36 | 37 | * Start initialization, write pre-connected server address, server port number, server toke 38 | * Add tunnel 39 | * Tcp protocol, encryption/compression is optional, other required fields 40 | * Udp protocol, encryption/compression is optional, other required 41 | * Http protocol, encryption/compression is optional, custom domain name/pan-domain name is optional 42 | * Https protocol, encryption/compression is optional, custom domain name/pan-domain name is optional 43 | * Tunnel list, you can copy the contents of the share list 44 | 45 | ## Support situation 46 | 47 | * Support 48 | * Mobile phone free root 49 | * Custom add server 50 | * protocol tcp、udp、http、https 51 | * Encryption, compression 52 | * Dynamically add tunnels, delete tunnels (hot load) 53 | * Custom domain name, pan domain name 54 | * Sharing tunnel information 55 | * Background process 56 | * Support frps 0.13.0/0.15.0/0.16.0/0.17.0/0.18.0/0.21.0 57 | * not support 58 | * Save the configuration file (re-enter the software app and re-enter the information) 59 | 60 | 61 | ## Development Plan 62 | 63 | * Support for multiple frps server versions 0.13.0/0.15.0/0.16.0/0.17.0/0.18.0/0.21.0 64 | * Support IOS Apple app [https://github.com/TelDragon/frpc-IOS](https://github.com/TelDragon/frpc-IOS) 65 | * User platform login 66 | * Get frps server list information, provide user server selection match 67 | * Save configuration information 68 | * Discard the frpc.ini startup file and execute the command directly using the "execute" function 69 | 70 | ## Contributing frpc-Android 71 | 72 | Interested in getting involved? We would like to help you! 73 | 74 | * Take a look at our [issues list](https://github.com/TelDragon/frpc-Android/issues) and consider sending a Pull Request to **dev branch**. 75 | * If you want to add a new feature, please create an issue first to describe the new feature, as well as the implementation approach. Once a proposal is accepted, create an implementation of the new features and submit it as a pull request. 76 | * Sorry for my poor english and improvement for this document is welcome even some typo fix. 77 | * If you have some wonderful ideas, send email to 542867428@qq.com. 78 | 79 | **Note: We prefer you to give your advise in [issues](https://github.com/TelDragon/frpc-Android/issues), so others with a same question can search it quickly and we don't need to answer them repeatly.** 80 | 81 | ## Donation 82 | 83 | * If [frp-Android](https://github.com/TelDragon/frpc-Android) help you a lot, you can support us by: 84 | 85 | frp-Android-02.png frp-Android-02.png 86 | 87 | * thank 88 | 89 | [bjbjoaoa@qq.com](mailto:bjbjoaoa@qq.com) [momo@188.com](mailto:momo@188.com) [zqy1301@qq.com](mailto:zqy1301@qq.com) 90 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # frp-Android 2 | 3 | [README](https://github.com/FrpcCluster/frpc-Android/blob/master/README.md) | [中文介绍](https://github.com/FrpcCluster/frpc-Android/blob/master/README_zh.md) 4 | 5 | 6 | # 项目介绍 7 | 8 | frp 源项目 [https://github.com/fatedier/frp](https://github.com/fatedier/frp) 9 | 10 | frp 帮助网站 [https://www.frp.fun/](https://www.frp.fun/) 11 | 12 | QQ群 [222670265](https://jq.qq.com/?_wv=1027&k=5kkmkwa) 13 | 14 | 15 | ## 项目 demo 简要图 16 | 17 | frp-Android-02.png frp-Android-03.png frp-Android-04.png 18 | 19 | ## 开发说明 20 | 21 | * [编译流程](https://github.com/FrpcCluster/frpc-Android/blob/master/Compile_zh.md) 22 | * 软件[Android studio 3.2](http://www.android-studio.org/) 23 | * Android, .aar 库 24 | frp使用gombile实现了一份go代码编译为android和ios平台下面可以直接调用的sdk类库 25 | 26 | ## Android SDK 用法 27 | 在Android系统提供的sdk形式是一个后缀为.aar的类库文件,开发的时候只需要把arr类库文件引入android项目即可. 28 | 29 | * 导入包 30 | 31 | ```java 32 | import frpclib.Frpclib; 33 | ``` 34 | 35 | ## 项目逻辑 36 | 37 | * 启动初始化,写入预连接的服务器地址,服务器端口号,服务器toke 38 | * 添加隧道 39 | * tcp协议,加密/压缩为非必选项,其他必填项 40 | * udp协议,加密/压缩为非必选项,其他必填项 41 | * http协议,加密/压缩为非必选项,自定义域名/泛域名二选一必填项 42 | * https协议,加密/压缩为非必选项,自定义域名/泛域名二选一必填项 43 | * 隧道列表,可以复制分享列表内容 44 | 45 | ## 支持情况 46 | 47 | * 支持 48 | * 手机免root 49 | * 自定义添加服务器 50 | * 协议 tcp、udp、http、https 51 | * 加密、压缩 52 | * 动态添加隧道、删除隧道 (热加载) 53 | * 自定义域名、泛域名 54 | * 分享隧道信息 55 | * 后台运行 56 | * 支持 frps 0.13.0/0.15.0/0.16.0/0.17.0/0.18.0/0.21.0 57 | * 不支持 58 | * 保存配置文件(退出软件app后重新进入需再次填写信息) 59 | 60 | 61 | ## 开发计划 62 | 63 | * 支持多frps服务器版本 0.13.0/0.15.0/0.16.0/0.17.0/0.18.0/0.21.0 64 | * 支持IOS 苹果app [https://github.com/TelDragon/frpc-IOS](https://github.com/TelDragon/frpc-IOS) 65 | * 用户平台登录 66 | * 获取frps服务器列表信息,提供用户服务器选择匹配 67 | * 保存配置信息 68 | * 抛弃frpc.ini 启动文件,直接使用“execute”函数执行命令 69 | 70 | ## 为 frpc-Android 做贡献 71 | 72 | frpc-Android 是一个免费且开源的项目,我们欢迎任何人为其开发和进步贡献力量。 73 | 74 | * 在使用过程中出现任何问题,可以通过 [issues](https://github.com/TelDragon/frpc-Android/issues) 来反馈。 75 | * Bug 的修复可以直接提交 Pull Request 到 dev 分支。 76 | * 如果是增加新的功能特性,请先创建一个 issue 并做简单描述以及大致的实现方法,提议被采纳后,就可以创建一个实现新特性的 Pull Request。 77 | * 欢迎对说明文档做出改善,帮助更多的人使用 frp,特别是英文文档。 78 | * 贡献代码请提交 PR 至 dev 分支,master 分支仅用于发布稳定可用版本。 79 | * 如果你有任何其他方面的问题,欢迎反馈至 542867428@qq.com 共同交流。 80 | 81 | **提醒:和项目相关的问题最好在 [issues](https://github.com/TelDragon/frpc-Android/issues) 中反馈,这样方便其他有类似问题的人可以快速查找解决方法,并且也避免了我们重复回答一些问题。** 82 | 83 | ## 捐助 84 | 85 | * 如果您觉得[frpc-Android](https://github.com/TelDragon/frpc-Android)对你有帮助,欢迎给予我们一定的捐助来维持项目的长期发展。 86 | 87 | frp-Android-02.png frp-Android-02.png 88 | 89 | * 感谢 90 | 91 | [bjbjoaoa@qq.com](mailto:bjbjoaoa@qq.com) [momo@188.com](mailto:momo@188.com) [zqy1301@qq.com](mailto:zqy1301@qq.com) 92 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html 3 | 4 | # Sets the minimum version of CMake required to build the native library. 5 | 6 | cmake_minimum_required(VERSION 3.4.1) 7 | 8 | # Creates and names a library, sets it as either STATIC 9 | # or SHARED, and provides the relative paths to its source code. 10 | # You can define multiple libraries, and CMake builds them for you. 11 | # Gradle automatically packages shared libraries with your APK. 12 | 13 | add_library( # Sets the name of the library. 14 | native-lib 15 | 16 | # Sets the library as a shared library. 17 | SHARED 18 | 19 | # Provides a relative path to your source file(s). 20 | src/main/cpp/native-lib.cpp ) 21 | 22 | # Searches for a specified prebuilt library and stores the path as a 23 | # variable. Because CMake includes system libraries in the search path by 24 | # default, you only need to specify the name of the public NDK library 25 | # you want to add. CMake verifies that the library exists before 26 | # completing its build. 27 | 28 | find_library( # Sets the name of the path variable. 29 | log-lib 30 | 31 | # Specifies the name of the NDK library that 32 | # you want CMake to locate. 33 | log ) 34 | 35 | # Specifies libraries CMake should link to your target library. You 36 | # can link multiple libraries, such as libraries you define in this 37 | # build script, prebuilt third-party libraries, or system libraries. 38 | 39 | target_link_libraries( # Specifies the target library. 40 | native-lib 41 | 42 | # Links the target library to the log library 43 | # included in the NDK. 44 | ${log-lib} ) -------------------------------------------------------------------------------- /app/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFM880/frpc-Android/12d3dc849958eae64a42bb640b85048444bd1ee8/app/app-release.apk -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'org.greenrobot.greendao' 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion '26.0.1' 6 | defaultConfig { 7 | applicationId "com.frp.fun" 8 | minSdkVersion 15 9 | // targetSdkVersion 26 10 | versionCode 3 11 | versionName "0.21.0" 12 | javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } } 13 | // testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | repositories { 23 | jcenter() 24 | flatDir { 25 | dirs 'libs' // aar目录 26 | } 27 | } 28 | lintOptions { 29 | checkReleaseBuilds false 30 | abortOnError false 31 | } 32 | buildToolsVersion = '28.0.2' 33 | } 34 | greendao { 35 | //版本号,升级时可配置 36 | schemaVersion 1 37 | targetGenDir 'src/main/java' 38 | daoPackage 'com.activity.greendao' 39 | } 40 | dependencies { 41 | implementation fileTree(include: ['*.jar'], dir: 'libs') 42 | implementation 'org.greenrobot:greendao:3.2.2' 43 | implementation 'com.android.support:recyclerview-v7:26.0.0-alpha1' 44 | implementation 'com.jakewharton:butterknife:7.0.1' 45 | implementation 'com.android.support:support-v4:26.0.0-alpha1' 46 | implementation 'com.android.support:appcompat-v7:26.0.0-alpha1' 47 | implementation 'com.squareup.okhttp3:logging-interceptor:3.3.0' 48 | implementation(name: 'frpclib', ext: 'aar') 49 | // compile 'cn.carbs.android:ExpandableTextView:1.0.0' 50 | } 51 | -------------------------------------------------------------------------------- /app/libs/frpclib-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFM880/frpc-Android/12d3dc849958eae64a42bb640b85048444bd1ee8/app/libs/frpclib-sources.jar -------------------------------------------------------------------------------- /app/libs/frpclib.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFM880/frpc-Android/12d3dc849958eae64a42bb640b85048444bd1ee8/app/libs/frpclib.aar -------------------------------------------------------------------------------- /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/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/config.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFM880/frpc-Android/12d3dc849958eae64a42bb640b85048444bd1ee8/app/src/main/assets/config.ini -------------------------------------------------------------------------------- /app/src/main/assets/frpc.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFM880/frpc-Android/12d3dc849958eae64a42bb640b85048444bd1ee8/app/src/main/assets/frpc.log -------------------------------------------------------------------------------- /app/src/main/cpp/native-lib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern "C" 5 | JNIEXPORT jstring 6 | 7 | JNICALL 8 | Java_com_iotserv_frpc_frpc_MainActivity_stringFromJNI( 9 | JNIEnv *env, 10 | jobject /* this */) { 11 | std::string hello = "Hello from C++"; 12 | return env->NewStringUTF(hello.c_str()); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFM880/frpc-Android/12d3dc849958eae64a42bb640b85048444bd1ee8/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/activity/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.LayoutRes; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.util.Log; 7 | 8 | 9 | import com.frpc.R; 10 | 11 | import butterknife.ButterKnife; 12 | 13 | /** 14 | * Created by lyf on 2016/1/10. 15 | */ 16 | public class BaseActivity extends AppCompatActivity { 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | Log.d("Base", "onCreate: " + getClass().getName()); 22 | } 23 | 24 | @Override 25 | public void setContentView(@LayoutRes int layoutResID) { 26 | super.setContentView(layoutResID); 27 | ButterKnife.bind(this); 28 | } 29 | 30 | @Override 31 | public void finish() { 32 | super.finish(); 33 | overridePendingTransition(R.anim.left_slip_enter_anim, R.anim.right_slip_exit_anim); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/BaseRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.AdapterView; 9 | 10 | 11 | import com.frpc.R; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * Created by lyf on 2016/3/17. 17 | */ 18 | public class BaseRecyclerAdapter extends RecyclerView.Adapter { 19 | private String TAG = "BaseRecyclerAdapter"; 20 | protected final int TYPE_FOOTER = 0; 21 | protected final int TYPE_HEADER = 2; 22 | protected final int TYPE_1 = 1; 23 | protected Context mContext; 24 | protected ItemViewCreator mItemViewCreator; 25 | protected List mList; 26 | protected boolean mShowFooter = true; 27 | protected View headView; 28 | private AdapterView.OnItemClickListener mOnItemClickListener; 29 | 30 | public BaseRecyclerAdapter(Context context, ItemViewCreator creator, List list){ 31 | this.mContext = context; 32 | this.mItemViewCreator = creator; 33 | this.mList = list; 34 | } 35 | @Override 36 | public int getItemViewType(int position) { 37 | if(headView != null && position == 0){ 38 | return TYPE_HEADER; 39 | }else if(isShowFooter() && position + 1 == getItemCount()){ 40 | return TYPE_FOOTER; 41 | }else{ 42 | return TYPE_1; 43 | } 44 | } 45 | 46 | @Override 47 | public int getItemCount() { 48 | int begin = headView == null ? 0 : 1; 49 | int end = isShowFooter() ? 1 : 0; 50 | return begin + mList.size() + end; 51 | } 52 | 53 | public void addHeadView(View view){ 54 | this.headView = view; 55 | } 56 | public void setOnItemClickListener(AdapterView.OnItemClickListener listener){ 57 | this.mOnItemClickListener = listener; 58 | } 59 | 60 | @Override 61 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 62 | View convertView; 63 | IItemView itemView; 64 | if(viewType == TYPE_HEADER){ 65 | ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT); 66 | headView.setLayoutParams(lp); 67 | return new FooterViewHolder(headView) ; 68 | }else if(viewType == TYPE_FOOTER){ 69 | return new FooterViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_comm_footer,parent,false)); 70 | }else { 71 | convertView = mItemViewCreator.newContentView(LayoutInflater.from(mContext),parent,viewType); 72 | itemView = mItemViewCreator.newItemView(convertView,viewType); 73 | convertView.setTag(R.id.itemview,itemView); 74 | itemView.onBindView(convertView); 75 | return (BaseViewHolder)itemView; 76 | } 77 | 78 | } 79 | 80 | @Override 81 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 82 | if(holder instanceof BaseViewHolder){ 83 | BaseViewHolder h = (BaseViewHolder) holder; 84 | int realPosition = headView == null ? position : position - 1; 85 | h.setPosition(realPosition); 86 | h.setOnClickListener(mItenClickListener); 87 | h.onBindData(mList.get(realPosition),position); 88 | if(mOnItemClickListener != null){ 89 | h.itemView.setOnClickListener(mItenClickListener); 90 | }else { 91 | h.itemView.setOnClickListener(null); 92 | } 93 | 94 | } 95 | } 96 | 97 | public void isShowFooter(boolean showFooter) { 98 | this.mShowFooter = showFooter; 99 | } 100 | 101 | public boolean isShowFooter() { 102 | return this.mShowFooter; 103 | } 104 | 105 | public class FooterViewHolder extends RecyclerView.ViewHolder { 106 | public FooterViewHolder(View view) { 107 | super(view); 108 | } 109 | } 110 | View.OnLongClickListener mLognClickListener = new View.OnLongClickListener(){ 111 | 112 | @Override 113 | public boolean onLongClick(View v) { 114 | return false; 115 | } 116 | }; 117 | View.OnClickListener mItenClickListener = new View.OnClickListener() { 118 | @Override 119 | public void onClick(View v) { 120 | IItemView itemView = (IItemView)v.getTag(R.id.itemview); 121 | if(itemView != null){ 122 | mOnItemClickListener.onItemClick(null,itemView.getConvertView(),itemView.position(),getItemId(itemView.position())); 123 | }else { 124 | IItemView subItemView = (IItemView)v.getTag(R.id.sub_itemview); 125 | mOnItemClickListener.onItemClick(null,v,subItemView.position(),v.getId()); 126 | } 127 | } 128 | }; 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/BaseRecyclerViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.LayoutRes; 5 | import android.view.View; 6 | import android.widget.AdapterView; 7 | import android.widget.TextView; 8 | 9 | 10 | import com.frpc.R; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import butterknife.OnClick; 16 | 17 | /** 18 | * Created by lyf on 2016/7/3. 19 | */ 20 | 21 | public abstract class BaseRecyclerViewActivity extends BaseActivity implements 22 | IRecyclerView,AdapterView.OnItemClickListener,CustomRecyclerView.LoadNextPageListener{ 23 | // @Bind(R.id.recycleview) 24 | public CustomRecyclerView mRecyclerView; 25 | // @Bind(R.id.titleTextView) 26 | public TextView titleTv; 27 | protected BaseRecyclerAdapter adapter; 28 | protected int pageIndex; 29 | protected List mList = new ArrayList<>(); 30 | private BaseRecyclerAdapter mAdapter; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | } 36 | 37 | @Override 38 | public void setContentView(@LayoutRes int layoutResID) { 39 | super.setContentView(layoutResID); 40 | initView(); 41 | } 42 | 43 | protected void initView(){ 44 | mRecyclerView = (CustomRecyclerView) findViewById(R.id.recycleview); 45 | titleTv = (TextView)findViewById(R.id.titleTextView); 46 | pageIndex = 1; 47 | mAdapter = newAdapter(); 48 | configRecyclerView(); 49 | mRecyclerView.setLoadNextPageListener(this); 50 | mAdapter.setOnItemClickListener(this); 51 | mRecyclerView.setAdapter(mAdapter); 52 | } 53 | 54 | protected abstract void requestData(); 55 | 56 | protected void configRecyclerView(){} 57 | 58 | public BaseRecyclerAdapter getAdapter(){ 59 | return mAdapter; 60 | } 61 | 62 | @Override 63 | public void onItemClick(AdapterView parent, View view, int position, long id) { 64 | 65 | } 66 | 67 | @Override 68 | public BaseRecyclerAdapter newAdapter() { 69 | return new BaseRecyclerAdapter(this,configItemViewCreator(),mList); 70 | } 71 | 72 | @Override 73 | public void onLoadNextPage() { 74 | requestData(); 75 | } 76 | 77 | // @OnClick(R.id.back) 78 | // public void back(){ 79 | // finish(); 80 | // } 81 | protected void onRefreshFinish(){} 82 | 83 | protected void onRefreshSucceed(List list){ 84 | onRefreshFinish(); 85 | if(pageIndex == 1){ 86 | mList.clear(); 87 | } 88 | mList.addAll(list); 89 | if(list.size() < 20){ 90 | mAdapter.isShowFooter(false); 91 | } 92 | mAdapter.notifyDataSetChanged(); 93 | pageIndex++; 94 | } 95 | 96 | protected void onRefreshFailed(){} 97 | 98 | protected void showEmptyView(){} 99 | 100 | protected void showNetErroView(){} 101 | 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/BaseRecyclerViewSwipeRefreshActivity.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.support.annotation.LayoutRes; 4 | import android.support.v4.widget.SwipeRefreshLayout; 5 | 6 | import com.frpc.R; 7 | 8 | 9 | /** 10 | * Created by lyf on 2016/6/22. 11 | */ 12 | 13 | public abstract class BaseRecyclerViewSwipeRefreshActivity extends BaseRecyclerViewActivity implements 14 | SwipeRefreshLayout.OnRefreshListener{ 15 | // @Bind(R.id.swipe_refresh_widget) 16 | protected SwipeRefreshLayout mSwipeRefreshLayout; 17 | 18 | 19 | @Override 20 | public void setContentView(@LayoutRes int layoutResID) { 21 | super.setContentView(layoutResID); 22 | // initView(); 23 | } 24 | 25 | protected void initView(){ 26 | super.initView(); 27 | mSwipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.swipe_refresh_widget); 28 | setupSwipeRefreshLayout(); 29 | } 30 | 31 | protected void setupSwipeRefreshLayout(){ 32 | mSwipeRefreshLayout.setColorSchemeResources(R.color.colorAccent); 33 | mSwipeRefreshLayout.setOnRefreshListener(this); 34 | } 35 | 36 | @Override 37 | public void onRefresh() { 38 | pageIndex = 1; 39 | requestData(); 40 | } 41 | 42 | protected void onRefreshFinish() { 43 | if(pageIndex == 1){ 44 | mSwipeRefreshLayout.setRefreshing(false); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/BaseViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | import butterknife.ButterKnife; 7 | 8 | 9 | /** 10 | * Created by lyf on 2016/6/15. 11 | */ 12 | public abstract class BaseViewHolder extends RecyclerView.ViewHolder implements IItemView { 13 | private int position; 14 | private View.OnClickListener mOnClickListener; 15 | public BaseViewHolder(View itemView) { 16 | super(itemView); 17 | } 18 | 19 | @Override 20 | public void onBindView(View view) { 21 | ButterKnife.bind(this,view); 22 | } 23 | 24 | public void setPosition(int position){ 25 | this.position = position; 26 | } 27 | 28 | @Override 29 | public int position(){ 30 | return this.position; 31 | } 32 | 33 | @Override 34 | public View getConvertView(){ 35 | return this.itemView; 36 | } 37 | 38 | public void setOnClickListener(View.OnClickListener listener){ 39 | this.mOnClickListener = listener; 40 | } 41 | 42 | public View.OnClickListener getOnClickListener(){ 43 | return mOnClickListener; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/CustomRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.LinearLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.util.AttributeSet; 7 | 8 | 9 | /** 10 | * Created by lyf on 2016/6/13. 11 | */ 12 | public class CustomRecyclerView extends RecyclerView { 13 | private LinearLayoutManager mLinearLayoutManager; 14 | // private DividerItemDecoration mDividerItemDecoration; 15 | private int lastEndItem; 16 | private LoadNextPageListener mLoadNextPageListener; 17 | // private BaseRecyclerAdapter adapter; 18 | public CustomRecyclerView(Context context) { 19 | this(context,null); 20 | } 21 | public CustomRecyclerView(Context context, AttributeSet attrs) { 22 | this(context, attrs,0); 23 | } 24 | public CustomRecyclerView(Context context, AttributeSet attrs, int defStyle) { 25 | super(context, attrs, defStyle); 26 | mLinearLayoutManager = new LinearLayoutManager(context); 27 | setLayoutManager(mLinearLayoutManager); 28 | // mDividerItemDecoration = new DividerItemDecoration(context,DividerItemDecoration.VERTICAL_LIST); 29 | // addItemDecoration(mDividerItemDecoration); 30 | addOnScrollListener(mOnScrollListener); 31 | } 32 | 33 | public RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener(){ 34 | @Override 35 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 36 | super.onScrolled(recyclerView, dx, dy); 37 | } 38 | 39 | @Override 40 | public void onScrollStateChanged(RecyclerView recyclerView, 41 | int newState) { 42 | int lastVisiblePosition = mLinearLayoutManager.findLastCompletelyVisibleItemPosition(); 43 | if (newState == RecyclerView.SCROLL_STATE_IDLE && getAdapter() != null){ 44 | int lastPosition = getAdapter().getItemCount() - 1; 45 | if(lastVisiblePosition == lastPosition){ 46 | if(getAdapter() instanceof BaseRecyclerAdapter){ 47 | BaseRecyclerAdapter adapter = (BaseRecyclerAdapter)getAdapter(); 48 | if(mLoadNextPageListener != null && lastVisiblePosition != lastEndItem && adapter.isShowFooter()){ 49 | mLoadNextPageListener.onLoadNextPage(); 50 | } 51 | } 52 | 53 | 54 | } 55 | lastEndItem = lastVisiblePosition; 56 | } 57 | } 58 | }; 59 | 60 | public void setLoadNextPageListener(LoadNextPageListener l){ 61 | this.mLoadNextPageListener = l; 62 | } 63 | public interface LoadNextPageListener { 64 | void onLoadNextPage(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/DividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.activity; 18 | 19 | 20 | import android.content.Context; 21 | import android.graphics.Canvas; 22 | import android.graphics.Rect; 23 | import android.graphics.drawable.ColorDrawable; 24 | import android.graphics.drawable.Drawable; 25 | import android.support.v4.view.ViewCompat; 26 | import android.support.v7.widget.LinearLayoutManager; 27 | import android.support.v7.widget.RecyclerView; 28 | import android.view.View; 29 | 30 | import com.frpc.R; 31 | 32 | 33 | public class DividerItemDecoration extends RecyclerView.ItemDecoration { 34 | 35 | // private static final int[] ATTRS = new int[]{ 36 | // R.attr.divider 37 | // }; 38 | 39 | public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; 40 | 41 | public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; 42 | 43 | private Drawable mDivider; 44 | 45 | private int mOrientation; 46 | private int margin; 47 | private int corrected = 0; 48 | 49 | public DividerItemDecoration(Context context, int orientation) { 50 | margin = Utils.dip2px(context, 8); 51 | mDivider = new ColorDrawable(context.getResources().getColor(R.color.divider)); 52 | setOrientation(orientation); 53 | 54 | } 55 | public DividerItemDecoration(Context context, int orientation, int corrected) { 56 | margin = Utils.dip2px(context, 8); 57 | mDivider = new ColorDrawable(context.getResources().getColor(R.color.divider)); 58 | setOrientation(orientation); 59 | this.corrected = corrected; 60 | } 61 | 62 | public void setOrientation(int orientation) { 63 | if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { 64 | throw new IllegalArgumentException("invalid orientation"); 65 | } 66 | mOrientation = orientation; 67 | } 68 | 69 | @Override 70 | public void onDraw(Canvas c, RecyclerView parent) { 71 | if (mOrientation == VERTICAL_LIST) { 72 | drawVertical(c, parent); 73 | } else { 74 | drawHorizontal(c, parent); 75 | } 76 | } 77 | 78 | public void drawVertical(Canvas c, RecyclerView parent) { 79 | final int left = parent.getPaddingLeft() + margin; 80 | final int right = parent.getWidth() - parent.getPaddingRight() - margin; 81 | 82 | final int childCount = parent.getChildCount(); 83 | for (int i = 0; i < childCount-corrected; i++) { 84 | final View child = parent.getChildAt(i); 85 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 86 | .getLayoutParams(); 87 | final int top = child.getBottom() + params.bottomMargin + 88 | Math.round(ViewCompat.getTranslationY(child)); 89 | // final int bottom = top + mDivider.getIntrinsicHeight(); 90 | final int bottom = top + 1; 91 | mDivider.setBounds(left, top, right, bottom); 92 | mDivider.draw(c); 93 | } 94 | } 95 | 96 | public void drawHorizontal(Canvas c, RecyclerView parent) { 97 | final int top = parent.getPaddingTop(); 98 | final int bottom = parent.getHeight() - parent.getPaddingBottom(); 99 | 100 | final int childCount = parent.getChildCount(); 101 | for (int i = 0; i < childCount; i++) { 102 | final View child = parent.getChildAt(i); 103 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child 104 | .getLayoutParams(); 105 | final int left = child.getRight() + params.rightMargin + 106 | Math.round(ViewCompat.getTranslationX(child)); 107 | final int right = left + mDivider.getIntrinsicHeight(); 108 | mDivider.setBounds(left, top, right, bottom); 109 | mDivider.draw(c); 110 | } 111 | } 112 | 113 | @Override 114 | public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { 115 | if (mOrientation == VERTICAL_LIST) { 116 | outRect.set(0, 0, 0, 1); 117 | } else { 118 | outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.content.Context; 4 | import android.content.res.AssetManager; 5 | import android.os.Environment; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | 9 | import java.io.BufferedInputStream; 10 | import java.io.BufferedOutputStream; 11 | import java.io.ByteArrayInputStream; 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.FileNotFoundException; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.io.ObjectInputStream; 19 | import java.io.ObjectOutputStream; 20 | import java.io.OutputStream; 21 | import java.io.UnsupportedEncodingException; 22 | 23 | public class FileUtils { 24 | public static String cachePath; 25 | public static String downloadPath; 26 | public static String imagePath; 27 | 28 | 29 | public static void initSDCard(Context context) { 30 | File f = getDiskCacheDir(context, "engineer51"); 31 | if (!f.exists()) { 32 | f.mkdirs(); 33 | } 34 | String cache = f.getPath() + File.separator; 35 | 36 | File file = new File(cache + "download" 37 | + File.separator); 38 | if (!file.exists()) { 39 | file.mkdirs(); 40 | } 41 | downloadPath = file.getPath() + File.separator ; 42 | 43 | File file2 = new File(cache + "cache" 44 | + File.separator); 45 | if (!file2.exists()) { 46 | file2.mkdirs(); 47 | } 48 | cachePath = file2.getPath() + File.separator; 49 | 50 | File file3 = new File(cache + "image" 51 | + File.separator); 52 | if (!file3.exists()) { 53 | file3.mkdirs(); 54 | } 55 | imagePath = file3.getPath() + File.separator; 56 | LogUtils.d("initSDCard cachePath:", cache); 57 | 58 | 59 | } 60 | 61 | /** 62 | * 获取可以使用的缓存目录 63 | * 64 | * @param context 65 | * @param uniqueName 66 | * 目录名称 67 | * @return 68 | */ 69 | public static File getDiskCacheDir(Context context, String uniqueName) { 70 | final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment 71 | .getExternalStorageState()) ? getExternalCacheDir(context) 72 | .getPath() : context.getCacheDir().getPath(); 73 | return new File(cachePath + File.separator + uniqueName); 74 | } 75 | 76 | /** 77 | * 获取程序外部的缓存目录 78 | * 79 | * @param context 80 | * @return 81 | */ 82 | public static File getExternalCacheDir(Context context) { 83 | final String cacheDir = "/Android/data/" + context.getPackageName() 84 | + "/cache/"; 85 | return new File(Environment.getExternalStorageDirectory().getPath() 86 | + cacheDir); 87 | } 88 | 89 | public static void copyAssetsFile(Context context, String filename, 90 | String dstpath) { 91 | File dstfile = new File(dstpath + filename); 92 | if (!dstfile.exists()) { 93 | AssetManager am = context.getAssets(); 94 | try { 95 | InputStream is = am.open(filename); 96 | byte[] buffer = new byte[is.available()]; 97 | is.read(buffer); 98 | makesureFileExist2(dstpath + filename); 99 | FileOutputStream fos = new FileOutputStream(dstfile, false); 100 | fos.write(buffer); 101 | fos.close(); 102 | is.close(); 103 | } catch (IOException e) { 104 | // TODO Auto-generated catch block 105 | e.printStackTrace(); 106 | } 107 | 108 | } 109 | } 110 | 111 | public static void saveDataAsFile(Object d, String fileName) { 112 | File file = new File(cachePath + fileName); 113 | try { 114 | if (!file.exists()) { 115 | file.createNewFile(); 116 | } 117 | ObjectOutputStream oos = new ObjectOutputStream( 118 | new FileOutputStream(file)); 119 | oos.writeObject(d); 120 | oos.close(); 121 | } catch (FileNotFoundException e) { 122 | LogUtils.PST(e); 123 | } catch (IOException e) { 124 | Log.e("saveDataAsFile", cachePath + fileName); 125 | e.printStackTrace(); 126 | } 127 | } 128 | 129 | // public static BaseData readCacheDataFromFile(String name) { 130 | // return readDataFromFile("data", name); 131 | // } 132 | 133 | public static Object readDataFromFile(String path, String name) { 134 | String filename = path + File.separator + name; 135 | 136 | File file = new File(cachePath + filename); 137 | LogUtils.d("readDataFromFile", cachePath + filename); 138 | if (!file.exists()) { 139 | return null; 140 | } 141 | try { 142 | ObjectInputStream ois = new ObjectInputStream(new FileInputStream( 143 | file)); 144 | Object data = ois.readObject(); 145 | ois.close(); 146 | return data; 147 | } catch (Exception e) { 148 | e.printStackTrace(); 149 | if (file.exists()) { 150 | // 有时候莫名其妙的转换错误。。。 151 | file.delete(); 152 | return null; 153 | } 154 | } 155 | return null; 156 | // return readDataFromFile(filename); 157 | } 158 | 159 | public static Object readBaseDataFromFile(String fileName) { 160 | File file = new File(cachePath + fileName); 161 | LogUtils.d("readDataFromFile", cachePath + fileName); 162 | if (!file.exists()) { 163 | return null; 164 | } 165 | try { 166 | ObjectInputStream ois = new ObjectInputStream(new FileInputStream( 167 | file)); 168 | Object data = ois.readObject(); 169 | ois.close(); 170 | return data; 171 | } catch (Exception e) { 172 | e.printStackTrace(); 173 | if (file.exists()) { 174 | // 有时候莫名其妙的转换错误。。。 175 | file.delete(); 176 | return null; 177 | } 178 | } 179 | return null; 180 | } 181 | 182 | public static String readDataFromFile(String fileName) { 183 | String res = null; 184 | try { 185 | File f = new File(cachePath + fileName); 186 | if (!f.exists()) { 187 | return null; 188 | } 189 | FileInputStream fin = new FileInputStream(f); 190 | int length = fin.available(); 191 | if (length > 0) { 192 | byte[] buffer = new byte[length]; 193 | fin.read(buffer); 194 | res = new String(buffer, "utf-8"); 195 | } 196 | fin.close(); 197 | } catch (Exception e) { 198 | e.printStackTrace(); 199 | LogUtils.PST(e); 200 | } 201 | return res; 202 | } 203 | 204 | public static void saveAsFile(String data, String fileName) { 205 | try { 206 | saveAsFile(data.getBytes("utf-8"), fileName); 207 | } catch (UnsupportedEncodingException e) { 208 | // TODO Auto-generated catch block 209 | e.printStackTrace(); 210 | } 211 | } 212 | 213 | // 数组保存为文件 214 | public static void saveAsFile(byte[] data, String fileName) { 215 | try { 216 | InputStream is = new ByteArrayInputStream(data); 217 | FileUtils.saveAsFile(is, cachePath + fileName); 218 | is.close(); 219 | } catch (IOException e) { 220 | LogUtils.PST(e); 221 | } 222 | } 223 | 224 | // 输入流保存为文件 225 | public static void saveAsFile(InputStream inputStream, String fileName) { 226 | try { 227 | if (TextUtils.isEmpty(fileName)) { 228 | return; 229 | } 230 | // make sure this file exist 231 | makesureFileExist2(fileName); 232 | LogUtils.d("FileUitls","fileutils save:" + fileName); 233 | OutputStream os = new FileOutputStream(fileName, false); 234 | byte[] buf = new byte[255]; 235 | int len = 0; 236 | while ((len = inputStream.read(buf)) != -1) { 237 | os.write(buf, 0, len); 238 | } 239 | inputStream.close(); 240 | os.flush(); 241 | os.close(); 242 | } catch (IOException e) { 243 | LogUtils.PST(e); 244 | } 245 | } 246 | 247 | public static String convertFilenameFromUrl(String urlstr) { 248 | return String.valueOf(urlstr.hashCode()); 249 | } 250 | 251 | public static void saveFile(String pathDirectory, String fileName, 252 | Object object) { 253 | String filePath = pathDirectory + File.separator + fileName; 254 | makesureFileExist(filePath); 255 | saveDataAsFile(object, filePath); 256 | } 257 | 258 | // 确定指定文件是否存在,如果不存在,则创建空文件 259 | public static void makesureFileExist(String fileName) { 260 | if (TextUtils.isEmpty(fileName)) { 261 | return; 262 | } 263 | // file path 264 | fileName = cachePath + fileName; 265 | int index = fileName.lastIndexOf("/"); 266 | File file = null; 267 | if (index != -1) { 268 | String filePath = fileName.substring(0, index); 269 | file = new File(filePath); 270 | if (!file.exists()) { 271 | boolean ret = file.mkdirs(); 272 | LogUtils.d("FileUtils","mkdirs " + ret + " " + filePath); 273 | } 274 | } 275 | file = new File(fileName); 276 | if (!file.exists()) {// 确保文件存在 277 | try { 278 | file.createNewFile(); 279 | } catch (Exception e) { 280 | Log.e("FileUtils", fileName); 281 | LogUtils.PST(e); 282 | } 283 | } 284 | } 285 | 286 | public static void makesureFileExist2(String filename) { 287 | if (TextUtils.isEmpty(filename)) { 288 | return; 289 | } 290 | if (filename.endsWith("/")) { 291 | File folder = new File(filename); 292 | if (!folder.exists()) { 293 | folder.mkdirs(); 294 | } 295 | } else { 296 | int index = filename.lastIndexOf("/"); 297 | String filePath = filename.substring(0, index); 298 | File folder = new File(filePath); 299 | if (!folder.exists()) { 300 | folder.mkdirs(); 301 | } 302 | File file = new File(filename); 303 | if (!file.exists()) { 304 | try { 305 | file.createNewFile(); 306 | } catch (IOException e) { 307 | // TODO Auto-generated catch block 308 | e.printStackTrace(); 309 | } 310 | } 311 | } 312 | } 313 | 314 | 315 | // 获取缓存文件大小 316 | public static String getCacheSize(Context context) { 317 | File cacheFile = new File(cachePath); 318 | File save = new File(cachePath + "bin/"); 319 | long cacheSize = getCacheLegth(cacheFile) - getCacheLegth(save) ; 320 | return formatFileSize(cacheSize); 321 | } 322 | 323 | public static String formatFileSize(long length) { 324 | String result = null; 325 | int sub_string = 0; 326 | if (length >= 1073741824) { 327 | sub_string = String.valueOf((float) length / 1073741824).indexOf( 328 | "."); 329 | result = ((float) length / 1073741824 + "000").substring(0, 330 | sub_string + 3) + "GB"; 331 | } else if (length >= 1048576) { 332 | sub_string = String.valueOf((float) length / 1048576).indexOf("."); 333 | result = ((float) length / 1048576 + "000").substring(0, 334 | sub_string + 3) + "MB"; 335 | } else if (length >= 1024) { 336 | sub_string = String.valueOf((float) length / 1024).indexOf("."); 337 | result = ((float) length / 1024 + "000").substring(0, 338 | sub_string + 3) + "KB"; 339 | } else if (length < 1024) 340 | result = Long.toString(length) + "B"; 341 | return result; 342 | } 343 | public static long getCacheLegth(File f) { 344 | long length = 0; 345 | if (f != null && f.exists()) { 346 | File[] tempList = f.listFiles(); 347 | if (tempList != null) { 348 | for (int i = 0; i < tempList.length; i++) { 349 | if (tempList[i].isFile()) { 350 | // 文件 351 | File tempFile = tempList[i]; 352 | length += tempFile.length(); 353 | } 354 | if (tempList[i].isDirectory()) { 355 | // 文件夹 356 | length += getCacheLegth(tempList[i]); 357 | } 358 | } 359 | } 360 | } 361 | return length; 362 | } 363 | 364 | public static void clearCache(Context context) { 365 | deleteFileOrPath(cachePath); 366 | deleteFileOrPath(context.getCacheDir()); 367 | // deleteFileOrPath("/data/data/"+context.getPackageName() + 368 | // "/databases"); 369 | cachePath = null; 370 | } 371 | 372 | // 递归删除文件及文件夹 373 | public static void deleteFileOrPath(String path) { 374 | File file = new File(path); 375 | deleteFileOrPath(file); 376 | } 377 | 378 | // 递归删除文件及文件夹 379 | public static void deleteFileOrPath(File file) { 380 | if (!file.exists()) { 381 | return; 382 | } 383 | if (file.isFile()) { 384 | file.delete(); 385 | return; 386 | } 387 | 388 | if (file.isDirectory()) { 389 | if (file.getName().equals("bin")) { 390 | return; 391 | } 392 | File[] childFiles = file.listFiles(); 393 | if (childFiles == null || childFiles.length == 0) { 394 | return; 395 | } 396 | 397 | for (int i = 0; i < childFiles.length; i++) { 398 | deleteFileOrPath(childFiles[i]); 399 | } 400 | // final File to = new File(file.getAbsolutePath() + 401 | // System.currentTimeMillis()); 402 | // file.renameTo(to); 403 | // to.delete(); 404 | } 405 | } 406 | 407 | public static void copyFile(File res, File des) { 408 | InputStream is = null; 409 | OutputStream os = null; 410 | try { 411 | is = new BufferedInputStream(new FileInputStream(res)); 412 | os = new BufferedOutputStream(new FileOutputStream(des)); 413 | byte[] buffer = new byte[1024]; 414 | int i; 415 | while ((i = is.read(buffer)) != -1) { 416 | os.write(buffer, 0, i); 417 | } 418 | 419 | } catch (Exception e) { 420 | e.printStackTrace(); 421 | } finally { 422 | try { 423 | is.close(); 424 | os.close(); 425 | } catch (Exception e) { 426 | e.printStackTrace(); 427 | } 428 | } 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/IItemView.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by lyf on 2016/6/15. 7 | */ 8 | public interface IItemView { 9 | 10 | void onBindView(View view); 11 | 12 | void onBindData(T bean, int position); 13 | 14 | // int size(); 15 | 16 | int position(); 17 | 18 | View getConvertView(); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/IRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | /** 4 | * Created by lyf on 2016/6/17. 5 | */ 6 | public interface IRecyclerView { 7 | BaseRecyclerAdapter newAdapter(); 8 | ItemViewCreator configItemViewCreator(); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/IniFile.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.File; 6 | import java.io.FileNotFoundException; 7 | import java.io.FileReader; 8 | import java.io.FileWriter; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.io.OutputStream; 13 | import java.io.OutputStreamWriter; 14 | import java.io.UnsupportedEncodingException; 15 | import java.util.LinkedHashMap; 16 | import java.util.Map; 17 | import java.util.regex.Pattern; 18 | 19 | public class IniFile { 20 | 21 | /** 22 | * 点节 23 | * 24 | * @author liucf 25 | * 26 | */ 27 | public class Section { 28 | 29 | private String name; 30 | 31 | private Map values = new LinkedHashMap(); 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public void setName(String name) { 38 | this.name = name; 39 | } 40 | 41 | public void set(String key, Object value) { 42 | values.put(key, value); 43 | } 44 | 45 | public Object get(String key) { 46 | return values.get(key); 47 | } 48 | 49 | public Map getValues() { 50 | return values; 51 | } 52 | 53 | 54 | } 55 | 56 | /** 57 | * 换行符 58 | */ 59 | private String line_separator = null; 60 | 61 | /** 62 | * 编码 63 | */ 64 | private String charSet = "UTF-8"; 65 | 66 | private Map sections = new LinkedHashMap(); 67 | 68 | /** 69 | * 指定换行符 70 | * 71 | * @param line_separator 72 | */ 73 | public void setLineSeparator(String line_separator){ 74 | this.line_separator = line_separator; 75 | } 76 | 77 | /** 78 | * 指定编码 79 | * 80 | * @param charSet 81 | */ 82 | public void setCharSet(String charSet){ 83 | this.charSet = charSet; 84 | } 85 | 86 | /** 87 | * 设置值 88 | * 89 | * @param section 90 | * 节点 91 | * @param key 92 | * 属性名 93 | * @param value 94 | * 属性值 95 | */ 96 | public void set(String section, String key, Object value) { 97 | Section sectionObject = sections.get(section); 98 | if (sectionObject == null) 99 | sectionObject = new Section(); 100 | sectionObject.name = section; 101 | sectionObject.set(key, value); 102 | sections.put(section, sectionObject); 103 | } 104 | 105 | /** 106 | * 获取节点 107 | * 108 | * @param section 109 | * 节点名称 110 | * @return 111 | */ 112 | public Section get(String section){ 113 | return sections.get(section); 114 | } 115 | 116 | /** 117 | * 获取值 118 | * 119 | * @param section 120 | * 节点名称 121 | * @param key 122 | * 属性名称 123 | * @return 124 | */ 125 | public Object get(String section, String key) { 126 | return get(section, key, null); 127 | } 128 | 129 | /** 130 | * 获取值 131 | * 132 | * @param section 133 | * 节点名称 134 | * @param key 135 | * 属性名称 136 | * @param defaultValue 137 | * 如果为空返回默认值 138 | * @return 139 | */ 140 | public Object get(String section, String key, String defaultValue) { 141 | Section sectionObject = sections.get(section); 142 | if (sectionObject != null) { 143 | Object value = sectionObject.get(key); 144 | if (value == null || value.toString().trim().equals("")) 145 | return defaultValue; 146 | return value; 147 | } 148 | return null; 149 | } 150 | 151 | /** 152 | * 删除节点 153 | * 154 | * @param section 155 | * 节点名称 156 | */ 157 | public void remove(String section){ 158 | sections.remove(section); 159 | } 160 | 161 | /** 162 | * 删除属性 163 | * 164 | * @param section 165 | * 节点名称 166 | * @param key 167 | * 属性名称 168 | */ 169 | public void remove(String section,String key){ 170 | Section sectionObject = sections.get(section); 171 | if(sectionObject!=null)sectionObject.getValues().remove(key); 172 | } 173 | 174 | 175 | /** 176 | * 当前操作的文件对像 177 | */ 178 | private File file = null; 179 | 180 | public IniFile(){ 181 | 182 | } 183 | 184 | public IniFile(File file) { 185 | this.file = file; 186 | initFromFile(file); 187 | } 188 | public boolean isexists(){ 189 | if(this.file.exists()){ 190 | return true; 191 | } 192 | return false; 193 | } 194 | public IniFile(InputStream inputStream) { 195 | initFromInputStream(inputStream); 196 | } 197 | 198 | /** 199 | * 加载一个ini文件 200 | * 201 | * @param file 202 | */ 203 | public void load(File file){ 204 | this.file = file; 205 | initFromFile(file); 206 | } 207 | 208 | /** 209 | * 加载一个输入流 210 | * 211 | * @param inputStream 212 | */ 213 | public void load(InputStream inputStream){ 214 | initFromInputStream(inputStream); 215 | } 216 | 217 | /** 218 | * 写到输出流中 219 | * 220 | * @param outputStream 221 | */ 222 | public void save(OutputStream outputStream){ 223 | BufferedWriter bufferedWriter; 224 | try { 225 | bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream,charSet)); 226 | saveConfig(bufferedWriter); 227 | } catch (UnsupportedEncodingException e) { 228 | e.printStackTrace(); 229 | } 230 | } 231 | 232 | /** 233 | * 保存到文件 234 | * 235 | * @param file 236 | */ 237 | public void save(File file){ 238 | try { 239 | BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file)); 240 | saveConfig(bufferedWriter); 241 | } catch (IOException e) { 242 | e.printStackTrace(); 243 | } 244 | } 245 | 246 | /** 247 | * 保存到当前文件 248 | */ 249 | public void save(){ 250 | save(this.file); 251 | } 252 | 253 | /** 254 | * 从输入流初始化IniFile 255 | * 256 | * @param inputStream 257 | */ 258 | private void initFromInputStream(InputStream inputStream) { 259 | BufferedReader bufferedReader; 260 | try { 261 | bufferedReader = new BufferedReader(new InputStreamReader(inputStream,charSet)); 262 | toIniFile(bufferedReader); 263 | } catch (UnsupportedEncodingException e) { 264 | e.printStackTrace(); 265 | } 266 | } 267 | 268 | /** 269 | * 从文件初始化IniFile 270 | * 271 | * @param file 272 | */ 273 | private void initFromFile(File file) { 274 | BufferedReader bufferedReader; 275 | try { 276 | bufferedReader = new BufferedReader(new FileReader(file)); 277 | toIniFile(bufferedReader); 278 | } catch (FileNotFoundException e) { 279 | e.printStackTrace(); 280 | } 281 | } 282 | 283 | /** 284 | * 从BufferedReader 初始化IniFile 285 | * 286 | * @param bufferedReader 287 | */ 288 | private void toIniFile(BufferedReader bufferedReader) { 289 | String strLine; 290 | Section section = null; 291 | Pattern p = Pattern.compile("^\\[.*\\]$"); 292 | try { 293 | while ((strLine = bufferedReader.readLine()) != null) { 294 | if (p.matcher((strLine)).matches()) { 295 | strLine = strLine.trim(); 296 | section = new Section(); 297 | section.name = strLine.substring(1, strLine.length() - 1); 298 | sections.put(section.name, section); 299 | } else { 300 | String[] keyValue = strLine.split("="); 301 | if (keyValue.length == 2) { 302 | section.set(keyValue[0], keyValue[1]); 303 | } 304 | } 305 | } 306 | bufferedReader.close(); 307 | } catch (IOException e) { 308 | e.printStackTrace(); 309 | } 310 | } 311 | 312 | /** 313 | * 保存Ini文件 314 | * 315 | * @param bufferedWriter 316 | */ 317 | private void saveConfig(BufferedWriter bufferedWriter){ 318 | try { 319 | boolean line_spe = false; 320 | if(line_separator == null || line_separator.trim().equals(""))line_spe = false; 321 | for (Section section : sections.values()) { 322 | bufferedWriter.write("["+section.getName()+"]"); 323 | if(line_spe) 324 | bufferedWriter.write(line_separator); 325 | else 326 | bufferedWriter.newLine(); 327 | for (Map.Entry entry : section.getValues().entrySet()) { 328 | bufferedWriter.write(entry.getKey()); 329 | bufferedWriter.write("="); 330 | bufferedWriter.write(entry.getValue().toString()); 331 | if(line_spe) 332 | bufferedWriter.write(line_separator); 333 | else 334 | bufferedWriter.newLine(); 335 | } 336 | } 337 | bufferedWriter.close(); 338 | } catch (IOException e) { 339 | e.printStackTrace(); 340 | } 341 | } 342 | /* 343 | IniFile file = new IniFile(new File("D:/dev/idressworkmobile/ztest/src/ztest/FacePositive.ini")); 344 | System.out.println(file.get("Config0", "PoinX0")); 345 | // file.save(new File("D:/c.ini")); 346 | file.remove("ModelFace"); 347 | file.save(); 348 | 349 | IniFile file2 = new IniFile(); 350 | file2.set("Config", "属性1", "值1"); 351 | file2.set("Config", "属性2", "值2"); 352 | file2.set("Config1", "属性3", "值3"); 353 | file2.save(new File("d:/d.ini")); 354 | * */ 355 | } -------------------------------------------------------------------------------- /app/src/main/java/com/activity/ItemViewCreator.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | 7 | /** 8 | * Created by lyf on 2016/6/15. 9 | */ 10 | public interface ItemViewCreator { 11 | View newContentView(LayoutInflater inflater, ViewGroup parent, int viewType); 12 | 13 | IItemView newItemView(View view, int viewType); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/LogUtils.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import android.widget.Toast; 6 | 7 | /** 8 | * 日志管理类 9 | */ 10 | public class LogUtils { 11 | public static final boolean DEBUG = true; 12 | 13 | public static void v(String tag, String message) { 14 | if(DEBUG) { 15 | Log.v(tag, message); 16 | } 17 | } 18 | 19 | public static void DebugLog(String message){ 20 | if(DEBUG){ 21 | Log.d("liyongfu",message); 22 | } 23 | } 24 | 25 | public static void d(String tag, String message) { 26 | if(DEBUG) { 27 | Log.d(tag, message); 28 | } 29 | } 30 | 31 | public static void i(String tag, String message) { 32 | if(DEBUG) { 33 | Log.i(tag, message); 34 | } 35 | } 36 | 37 | 38 | public static void e(String tag, String message, Exception e) { 39 | if(DEBUG) { 40 | Log.e(tag, message, e); 41 | } 42 | } 43 | 44 | public static void ShowToast(Context context, String text) { 45 | if (context != null) 46 | Toast.makeText(context, text, Toast.LENGTH_SHORT).show(); 47 | } 48 | public static void ShowToast(Context context, String text, int duration) { 49 | if (context != null) 50 | Toast.makeText(context, text, duration).show(); 51 | } 52 | 53 | public static void ShowToast(Context context, int resid, int duration) { 54 | if (context != null) 55 | Toast.makeText(context, context.getString(resid), duration).show(); 56 | 57 | } 58 | public static void ShowToast(Context context, int resid) { 59 | if (context != null) 60 | Toast.makeText(context, context.getString(resid), Toast.LENGTH_SHORT).show(); 61 | 62 | } 63 | 64 | 65 | public static void e(String string, String string2) { 66 | if (DEBUG) 67 | Log.e(string, string2); 68 | } 69 | 70 | public static void w(String string, String string2) { 71 | if (DEBUG) 72 | Log.w(string, string2); 73 | } 74 | 75 | public static void PST(Exception e) { 76 | if (DEBUG) { 77 | e.printStackTrace(); 78 | } 79 | } 80 | 81 | public static void PST(OutOfMemoryError e) { 82 | if (DEBUG) { 83 | e.printStackTrace(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.content.res.AssetManager; 6 | import android.os.Bundle; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.EditText; 10 | import android.widget.Toast; 11 | 12 | import com.activity.greendao.DBSuiDaoHelper; 13 | import com.frpc.R; 14 | 15 | import java.io.File; 16 | import java.io.FileOutputStream; 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | 20 | 21 | /** 22 | * A login screen that offers login via email/password. 23 | */ 24 | public class LoginActivity extends Activity { 25 | 26 | private EditText Exservice_ip;//服务器ip 27 | private EditText Exservice_port;//服务器端口号 28 | private EditText Exservice_token;//服务器登录token 29 | private Button Btsave; 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_login); 34 | initView();//初始化控件 35 | initEvent();//加载事件 36 | } 37 | private void initView(){ 38 | Exservice_ip = findViewById(R.id.service_ip); 39 | Exservice_port = findViewById(R.id.service_port); 40 | Exservice_token = findViewById(R.id.service_token); 41 | Btsave = findViewById(R.id.save); 42 | } 43 | private void initEvent(){ 44 | 45 | Btsave.setOnClickListener(new View.OnClickListener() { 46 | @Override 47 | public void onClick(View v) { 48 | String ip = Exservice_ip.getText().toString(); 49 | String port = Exservice_port.getText().toString(); 50 | String token = Exservice_token.getText().toString(); 51 | if(port.length()>0){ 52 | int int_port = Integer.parseInt(port); 53 | if(ip.length()>2 && 65535>=int_port &&int_port>=1024 && token.length()>2){ 54 | DBSuiDaoHelper.deleteALL(); 55 | copyToSD(ip,port,token); 56 | Intent intent = new Intent(LoginActivity.this,knife_net_list.class); 57 | startActivity(intent); 58 | }else{ 59 | Toast.makeText(LoginActivity.this,"输入有误",Toast.LENGTH_SHORT).show(); 60 | } 61 | 62 | }else{ 63 | Toast.makeText(LoginActivity.this,"输入有误",Toast.LENGTH_SHORT).show(); 64 | } 65 | } 66 | }); 67 | } 68 | 69 | /** 70 | * @param service_ip 71 | * @param service_port 72 | * @param service_token 73 | * 向配置文件中写入FRP服务器的参数 74 | */ 75 | private void copyToSD(String service_ip,String service_port,String service_token) { 76 | InputStream in = null; 77 | FileOutputStream out = null; 78 | 79 | //判断如果数据库已经拷贝成功,不需要再次拷贝 80 | File file = new File(this.getExternalFilesDir(null), MyApplication.FILENAME); 81 | if(file.exists()){ 82 | file.delete(); 83 | } 84 | if (!file.exists()) { 85 | try { 86 | file.createNewFile(); 87 | AssetManager assets = getAssets(); 88 | //2.读取数据资源 89 | in = assets.open(MyApplication.FILENAME); 90 | out = new FileOutputStream(file); 91 | //3.读写操作 92 | byte[] b = new byte[1024];//缓冲区域 93 | int len = -1; //保存读取的长度 94 | while ((len = in.read(b)) != -1) { 95 | out.write(b, 0, len); 96 | } 97 | } catch (IOException e) { 98 | e.printStackTrace(); 99 | } 100 | } 101 | 102 | try { 103 | out = new FileOutputStream(file,true); 104 | String common = "[common]\r\n"; 105 | String server_addr = "server_addr = "+service_ip+"\r\n"; 106 | String server_port = "server_port = "+ service_port + "\r\n"; 107 | String token = "token = "+ service_token + "\r\n"; 108 | String admin_addr = "admin_addr = 0.0.0.0"+"\r\n"; 109 | String admin_port = "admin_port = 7400"+"\r\n"; 110 | String admin_user = "admin_user = admin"+"\r\n"; 111 | String admin_pwd = "admin_pwd = admin"+"\r\n"; 112 | String log_file = "log_file = /storage/emulated/0/Android/data/com.frp.fun/files/frpc.log"+"\r\n"; 113 | String log_level = "log_level = info"+"\r\n"; 114 | String log_max_days = "log_max_days = 3"+"\r\n"; 115 | String pool_count = "pool_count = 5"+"\r\n"; 116 | String tcp_mux = "tcp_mux = true"+"\r\n"; 117 | String login_fail_exit = "login_fail_exit = true"+"\r\n"; 118 | String protocol = "protocol = tcp"+"\r\n"; 119 | out.write(common.getBytes()); 120 | out.write(server_addr.getBytes()); 121 | out.write(server_port.getBytes()); 122 | out.write(token.getBytes()); 123 | out.write(admin_addr.getBytes()); 124 | out.write(admin_port.getBytes()); 125 | out.write(admin_user.getBytes()); 126 | out.write(admin_pwd.getBytes()); 127 | out.write(log_file.getBytes()); 128 | out.write(log_level.getBytes()); 129 | out.write(log_max_days.getBytes()); 130 | out.write(pool_count.getBytes()); 131 | out.write(tcp_mux.getBytes()); 132 | out.write(login_fail_exit.getBytes()); 133 | out.write(protocol.getBytes()); 134 | 135 | } catch (IOException e) { 136 | e.printStackTrace(); 137 | } finally { 138 | try { 139 | if (in != null) in.close(); 140 | if (out != null) out.close(); 141 | } catch (IOException e) { 142 | e.printStackTrace(); 143 | } 144 | } 145 | } 146 | } 147 | 148 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/MyApplication.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.content.res.AssetManager; 7 | import android.database.sqlite.SQLiteDatabase; 8 | 9 | import com.activity.greendao.DBSuiDaoHelper; 10 | import com.activity.greendao.DaoMaster; 11 | import com.activity.greendao.DaoSession; 12 | 13 | import java.io.File; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | 18 | public class MyApplication extends Application { 19 | 20 | public static Context mContext; 21 | public static String FILENAME = "config.ini"; 22 | private static DaoSession daoSession; 23 | @Override 24 | public void onCreate() { 25 | super.onCreate(); 26 | mContext = getApplicationContext(); 27 | copyToSD("config.ini"); 28 | setupDatabase(); 29 | } 30 | private void copyToSD(String dbName) { 31 | InputStream in=null; 32 | FileOutputStream out = null; 33 | //判断如果数据库已经拷贝成功,不需要再次拷贝 34 | File file = new File(this.getExternalFilesDir(null), dbName); 35 | if(file.exists()) { 36 | file.delete(); 37 | } 38 | try { 39 | file.createNewFile(); 40 | } catch (IOException e) { 41 | e.printStackTrace(); 42 | } 43 | //打开assets中保存的资源 44 | //1.获取assets目录的管理者 45 | AssetManager assets = getAssets(); 46 | 47 | try { 48 | //2.读取数据资源 49 | in = assets.open(dbName); 50 | //getFilesDir() : data -> data -> 应用程序的包名 -> files 51 | //getCacheDir() : data -> data -> 应用程序的包名 -> cache 52 | out = new FileOutputStream(file); 53 | 54 | //3.读写操作 55 | byte[] b = new byte[1024];//缓冲区域 56 | int len = -1; //保存读取的长度 57 | while((len = in.read(b)) != -1){ 58 | out.write(b, 0, len); 59 | } 60 | } catch (IOException e) { 61 | 62 | e.printStackTrace(); 63 | }finally{ 64 | 65 | if (in != null) { 66 | try { 67 | in.close(); 68 | } catch (IOException e) { 69 | 70 | e.printStackTrace(); 71 | } 72 | } 73 | if (out != null) { 74 | try { 75 | 76 | out.close(); 77 | } catch (IOException e) { 78 | 79 | e.printStackTrace(); 80 | } 81 | } 82 | } 83 | } 84 | private void setupDatabase() { 85 | //创建数据库shop.db 86 | DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "shop.db", null); 87 | //获取可写数据库 88 | SQLiteDatabase db = helper.getWritableDatabase(); 89 | //获取数据库对象 90 | DaoMaster daoMaster = new DaoMaster(db); 91 | //获取dao对象管理者 92 | daoSession = daoMaster.newSession(); 93 | DBSuiDaoHelper.queryAll(); 94 | DBSuiDaoHelper.deleteALL(); 95 | } 96 | public static DaoSession getDaoInstant() { 97 | return daoSession; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/MySuiDaoViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.content.ClipData; 4 | import android.content.ClipboardManager; 5 | import android.content.Context; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | import android.widget.Toast; 11 | 12 | import com.activity.greendao.SuiDao; 13 | import com.frpc.R; 14 | 15 | import butterknife.Bind; 16 | import butterknife.OnClick; 17 | 18 | 19 | /** 20 | * Created by Administrator on 2018/7/18. 21 | */ 22 | 23 | public class MySuiDaoViewHolder extends BaseViewHolder { 24 | public static final int LAYOUT_RES = R.layout.item_my_suidao; 25 | @Bind(R.id.circle)public ImageView circleBox; 26 | @Bind(R.id.name)public TextView nameTv; 27 | @Bind(R.id.time)public TextView timeTv; 28 | @Bind(R.id.info)public TextView infoTv; 29 | @Bind(R.id.link)public Button linkBt; 30 | private String link; 31 | private String type; 32 | public MySuiDaoViewHolder(View itemView) { 33 | super(itemView); 34 | } 35 | 36 | @Override 37 | public void onBindData(SuiDao bean, int position) { 38 | nameTv.setText(bean.name); 39 | timeTv.setText(bean.user+":"+bean.getIp()+":"+bean.getPort()); 40 | if(bean.getUser().equals("tcp") || bean.getUser().equals("udp")){ 41 | infoTv.setText("远程端口号:"+bean.getType()); 42 | }else{ 43 | infoTv.setText("域名:"+bean.getLink()); 44 | } 45 | link = bean.getLink(); 46 | type = bean.getUser(); 47 | } 48 | 49 | 50 | @OnClick(R.id.link) 51 | public void link(){ 52 | String remote_string=""; 53 | remote_string = nameTv.getText()+"\r\n"+timeTv.getText()+"\r\n"+infoTv.getText(); 54 | ClipboardManager cm = (ClipboardManager) MyApplication.mContext.getSystemService(Context.CLIPBOARD_SERVICE); 55 | // 创建普通字符型ClipData 56 | ClipData mClipData = ClipData.newPlainText("Label", remote_string); 57 | // 将ClipData内容放到系统剪贴板里。 58 | cm.setPrimaryClip(mClipData); 59 | Toast.makeText(MyApplication.mContext,"复制成功",Toast.LENGTH_SHORT).show(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.util.Log; 8 | 9 | import com.frpc.R; 10 | 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | import java.util.Set; 14 | 15 | 16 | 17 | /** 18 | * Created by lyf on 2016/6/22. 19 | */ 20 | public class SplashActivity extends Activity { 21 | private static final String TAG = "SpalshActivity"; 22 | Handler mHandler = new Handler(); 23 | private Intent websocketServiceIntent; 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_splash); 28 | 29 | mHandler.postDelayed(new Runnable() { 30 | @Override 31 | public void run() { 32 | Intent intent = new Intent(SplashActivity.this,LoginActivity.class); 33 | startActivity(intent); 34 | finish(); 35 | } 36 | }, 3000); 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/greendao/DBSuiDaoHelper.java: -------------------------------------------------------------------------------- 1 | package com.activity.greendao; 2 | 3 | 4 | import com.activity.MyApplication; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by Administrator on 2018/7/18. 10 | */ 11 | 12 | public class DBSuiDaoHelper { 13 | public static void insertsuidao(SuiDao suidao) { 14 | MyApplication.getDaoInstant().getSuiDaoDao().insertOrReplace(suidao); 15 | } 16 | 17 | /** 18 | * 删除数据 19 | * 20 | * @param id 21 | */ 22 | public static void deletesuidao(long id) { 23 | MyApplication.getDaoInstant().getSuiDaoDao().deleteByKey(id); 24 | } 25 | public static void deleteALL() { 26 | MyApplication.getDaoInstant().getSuiDaoDao().deleteAll(); 27 | } 28 | /** 29 | * 更新数据 30 | */ 31 | public static void updatesuidao(SuiDao shop) { 32 | MyApplication.getDaoInstant().getSuiDaoDao().update(shop); 33 | } 34 | 35 | /** 36 | * 查询所有数据 37 | * 38 | * @return 39 | */ 40 | public static List queryAll() { 41 | return MyApplication.getDaoInstant().getSuiDaoDao().loadAll(); 42 | } 43 | 44 | public static String quarySetionName(long id) { 45 | return MyApplication.getDaoInstant().getSuiDaoDao().load(id).getName(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/greendao/DaoMaster.java: -------------------------------------------------------------------------------- 1 | package com.activity.greendao; 2 | 3 | import android.content.Context; 4 | import android.database.sqlite.SQLiteDatabase; 5 | import android.database.sqlite.SQLiteDatabase.CursorFactory; 6 | import android.util.Log; 7 | 8 | import org.greenrobot.greendao.AbstractDaoMaster; 9 | import org.greenrobot.greendao.database.StandardDatabase; 10 | import org.greenrobot.greendao.database.Database; 11 | import org.greenrobot.greendao.database.DatabaseOpenHelper; 12 | import org.greenrobot.greendao.identityscope.IdentityScopeType; 13 | 14 | 15 | // THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. 16 | /** 17 | * Master of DAO (schema version 1): knows all DAOs. 18 | */ 19 | public class DaoMaster extends AbstractDaoMaster { 20 | public static final int SCHEMA_VERSION = 1; 21 | 22 | /** Creates underlying database table using DAOs. */ 23 | public static void createAllTables(Database db, boolean ifNotExists) { 24 | SuiDaoDao.createTable(db, ifNotExists); 25 | } 26 | 27 | /** Drops underlying database table using DAOs. */ 28 | public static void dropAllTables(Database db, boolean ifExists) { 29 | SuiDaoDao.dropTable(db, ifExists); 30 | } 31 | 32 | /** 33 | * WARNING: Drops all table on Upgrade! Use only during development. 34 | * Convenience method using a {@link DevOpenHelper}. 35 | */ 36 | public static DaoSession newDevSession(Context context, String name) { 37 | Database db = new DevOpenHelper(context, name).getWritableDb(); 38 | DaoMaster daoMaster = new DaoMaster(db); 39 | return daoMaster.newSession(); 40 | } 41 | 42 | public DaoMaster(SQLiteDatabase db) { 43 | this(new StandardDatabase(db)); 44 | } 45 | 46 | public DaoMaster(Database db) { 47 | super(db, SCHEMA_VERSION); 48 | registerDaoClass(SuiDaoDao.class); 49 | } 50 | 51 | public DaoSession newSession() { 52 | return new DaoSession(db, IdentityScopeType.Session, daoConfigMap); 53 | } 54 | 55 | public DaoSession newSession(IdentityScopeType type) { 56 | return new DaoSession(db, type, daoConfigMap); 57 | } 58 | 59 | /** 60 | * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} - 61 | */ 62 | public static abstract class OpenHelper extends DatabaseOpenHelper { 63 | public OpenHelper(Context context, String name) { 64 | super(context, name, SCHEMA_VERSION); 65 | } 66 | 67 | public OpenHelper(Context context, String name, CursorFactory factory) { 68 | super(context, name, factory, SCHEMA_VERSION); 69 | } 70 | 71 | @Override 72 | public void onCreate(Database db) { 73 | Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION); 74 | createAllTables(db, false); 75 | } 76 | } 77 | 78 | /** WARNING: Drops all table on Upgrade! Use only during development. */ 79 | public static class DevOpenHelper extends OpenHelper { 80 | public DevOpenHelper(Context context, String name) { 81 | super(context, name); 82 | } 83 | 84 | public DevOpenHelper(Context context, String name, CursorFactory factory) { 85 | super(context, name, factory); 86 | } 87 | 88 | @Override 89 | public void onUpgrade(Database db, int oldVersion, int newVersion) { 90 | Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables"); 91 | dropAllTables(db, true); 92 | onCreate(db); 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/greendao/DaoSession.java: -------------------------------------------------------------------------------- 1 | package com.activity.greendao; 2 | 3 | import java.util.Map; 4 | 5 | import org.greenrobot.greendao.AbstractDao; 6 | import org.greenrobot.greendao.AbstractDaoSession; 7 | import org.greenrobot.greendao.database.Database; 8 | import org.greenrobot.greendao.identityscope.IdentityScopeType; 9 | import org.greenrobot.greendao.internal.DaoConfig; 10 | 11 | import com.activity.greendao.SuiDao; 12 | 13 | import com.activity.greendao.SuiDaoDao; 14 | 15 | // THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. 16 | 17 | /** 18 | * {@inheritDoc} 19 | * 20 | * @see org.greenrobot.greendao.AbstractDaoSession 21 | */ 22 | public class DaoSession extends AbstractDaoSession { 23 | 24 | private final DaoConfig suiDaoDaoConfig; 25 | 26 | private final SuiDaoDao suiDaoDao; 27 | 28 | public DaoSession(Database db, IdentityScopeType type, Map>, DaoConfig> 29 | daoConfigMap) { 30 | super(db); 31 | 32 | suiDaoDaoConfig = daoConfigMap.get(SuiDaoDao.class).clone(); 33 | suiDaoDaoConfig.initIdentityScope(type); 34 | 35 | suiDaoDao = new SuiDaoDao(suiDaoDaoConfig, this); 36 | 37 | registerDao(SuiDao.class, suiDaoDao); 38 | } 39 | 40 | public void clear() { 41 | suiDaoDaoConfig.clearIdentityScope(); 42 | } 43 | 44 | public SuiDaoDao getSuiDaoDao() { 45 | return suiDaoDao; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/greendao/HttpUtil.java: -------------------------------------------------------------------------------- 1 | package com.activity.greendao; 2 | 3 | import android.util.Log; 4 | 5 | import java.io.IOException; 6 | import okhttp3.Call; 7 | import okhttp3.Callback; 8 | import okhttp3.Credentials; 9 | import okhttp3.OkHttpClient; 10 | import okhttp3.Request; 11 | import okhttp3.Response; 12 | import okhttp3.Route; 13 | 14 | 15 | public class HttpUtil { 16 | public static void frpreload() { 17 | final String basic = Credentials.basic("admin", "admin"); 18 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 19 | builder.authenticator(new okhttp3.Authenticator() { 20 | @Override 21 | public Request authenticate(Route route, Response response) throws IOException { 22 | return response.request().newBuilder().header("Authorization", basic).build(); 23 | } 24 | }); 25 | OkHttpClient client = builder.build(); 26 | Request request = new Request.Builder().url("http://127.0.0.1:7400/api/reload").build(); 27 | client.newCall(request).enqueue(new Callback() { 28 | @Override 29 | public void onFailure(Call call, IOException e) { 30 | Log.d("google.sang", "onFailure: "+e.getMessage()); 31 | } 32 | 33 | @Override 34 | public void onResponse(Call call, Response response) throws IOException { 35 | if (response.isSuccessful()) { 36 | Log.d("google.sang", "onResponse: "+response.body().string()); 37 | } 38 | } 39 | }); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/greendao/SuiDao.java: -------------------------------------------------------------------------------- 1 | package com.activity.greendao; 2 | 3 | import org.greenrobot.greendao.annotation.Entity; 4 | import org.greenrobot.greendao.annotation.Id; 5 | import org.greenrobot.greendao.annotation.Generated; 6 | 7 | /** 8 | * Created by Administrator on 2018/7/18. 9 | */ 10 | @Entity 11 | public class SuiDao { 12 | @Id(autoincrement = true) 13 | private Long id; 14 | public String ip; 15 | public String port; 16 | public String type; 17 | public String link; 18 | public String name;// 19 | public String user;//隧道描述,方便用户分辨隧道 20 | public String time; 21 | @Generated(hash = 2141312454) 22 | public SuiDao(Long id, String ip, String port, String type, String link, 23 | String name, String user, String time) { 24 | this.id = id; 25 | this.ip = ip; 26 | this.port = port; 27 | this.type = type; 28 | this.link = link; 29 | this.name = name; 30 | this.user = user; 31 | this.time = time; 32 | } 33 | @Generated(hash = 1920169455) 34 | public SuiDao() { 35 | } 36 | public Long getId() { 37 | return this.id; 38 | } 39 | public void setId(Long id) { 40 | this.id = id; 41 | } 42 | public String getIp() { 43 | return this.ip; 44 | } 45 | public void setIp(String ip) { 46 | this.ip = ip; 47 | } 48 | public String getPort() { 49 | return this.port; 50 | } 51 | public void setPort(String port) { 52 | this.port = port; 53 | } 54 | public String getType() { 55 | return this.type; 56 | } 57 | public void setType(String type) { 58 | this.type = type; 59 | } 60 | public String getLink() { 61 | return this.link; 62 | } 63 | public void setLink(String link) { 64 | this.link = link; 65 | } 66 | public String getName() { 67 | return this.name; 68 | } 69 | public void setName(String name) { 70 | this.name = name; 71 | } 72 | public String getUser() { 73 | return this.user; 74 | } 75 | public void setUser(String user) { 76 | this.user = user; 77 | } 78 | public String getTime() { 79 | return this.time; 80 | } 81 | public void setTime(String time) { 82 | this.time = time; 83 | } 84 | 85 | 86 | 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/greendao/SuiDaoDao.java: -------------------------------------------------------------------------------- 1 | package com.activity.greendao; 2 | 3 | import android.database.Cursor; 4 | import android.database.sqlite.SQLiteStatement; 5 | 6 | import org.greenrobot.greendao.AbstractDao; 7 | import org.greenrobot.greendao.Property; 8 | import org.greenrobot.greendao.internal.DaoConfig; 9 | import org.greenrobot.greendao.database.Database; 10 | import org.greenrobot.greendao.database.DatabaseStatement; 11 | 12 | // THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT. 13 | /** 14 | * DAO for table "SUI_DAO". 15 | */ 16 | public class SuiDaoDao extends AbstractDao { 17 | 18 | public static final String TABLENAME = "SUI_DAO"; 19 | 20 | /** 21 | * Properties of entity SuiDao.
22 | * Can be used for QueryBuilder and for referencing column names. 23 | */ 24 | public static class Properties { 25 | public final static Property Id = new Property(0, Long.class, "id", true, "_id"); 26 | public final static Property Ip = new Property(1, String.class, "ip", false, "IP"); 27 | public final static Property Port = new Property(2, String.class, "port", false, "PORT"); 28 | public final static Property Type = new Property(3, String.class, "type", false, "TYPE"); 29 | public final static Property Link = new Property(4, String.class, "link", false, "LINK"); 30 | public final static Property Name = new Property(5, String.class, "name", false, "NAME"); 31 | public final static Property User = new Property(6, String.class, "user", false, "USER"); 32 | public final static Property Time = new Property(7, String.class, "time", false, "TIME"); 33 | } 34 | 35 | 36 | public SuiDaoDao(DaoConfig config) { 37 | super(config); 38 | } 39 | 40 | public SuiDaoDao(DaoConfig config, DaoSession daoSession) { 41 | super(config, daoSession); 42 | } 43 | 44 | /** Creates the underlying database table. */ 45 | public static void createTable(Database db, boolean ifNotExists) { 46 | String constraint = ifNotExists? "IF NOT EXISTS ": ""; 47 | db.execSQL("CREATE TABLE " + constraint + "\"SUI_DAO\" (" + // 48 | "\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id 49 | "\"IP\" TEXT," + // 1: ip 50 | "\"PORT\" TEXT," + // 2: port 51 | "\"TYPE\" TEXT," + // 3: type 52 | "\"LINK\" TEXT," + // 4: link 53 | "\"NAME\" TEXT," + // 5: name 54 | "\"USER\" TEXT," + // 6: user 55 | "\"TIME\" TEXT);"); // 7: time 56 | } 57 | 58 | /** Drops the underlying database table. */ 59 | public static void dropTable(Database db, boolean ifExists) { 60 | String sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + "\"SUI_DAO\""; 61 | db.execSQL(sql); 62 | } 63 | 64 | @Override 65 | protected final void bindValues(DatabaseStatement stmt, SuiDao entity) { 66 | stmt.clearBindings(); 67 | 68 | Long id = entity.getId(); 69 | if (id != null) { 70 | stmt.bindLong(1, id); 71 | } 72 | 73 | String ip = entity.getIp(); 74 | if (ip != null) { 75 | stmt.bindString(2, ip); 76 | } 77 | 78 | String port = entity.getPort(); 79 | if (port != null) { 80 | stmt.bindString(3, port); 81 | } 82 | 83 | String type = entity.getType(); 84 | if (type != null) { 85 | stmt.bindString(4, type); 86 | } 87 | 88 | String link = entity.getLink(); 89 | if (link != null) { 90 | stmt.bindString(5, link); 91 | } 92 | 93 | String name = entity.getName(); 94 | if (name != null) { 95 | stmt.bindString(6, name); 96 | } 97 | 98 | String user = entity.getUser(); 99 | if (user != null) { 100 | stmt.bindString(7, user); 101 | } 102 | 103 | String time = entity.getTime(); 104 | if (time != null) { 105 | stmt.bindString(8, time); 106 | } 107 | } 108 | 109 | @Override 110 | protected final void bindValues(SQLiteStatement stmt, SuiDao entity) { 111 | stmt.clearBindings(); 112 | 113 | Long id = entity.getId(); 114 | if (id != null) { 115 | stmt.bindLong(1, id); 116 | } 117 | 118 | String ip = entity.getIp(); 119 | if (ip != null) { 120 | stmt.bindString(2, ip); 121 | } 122 | 123 | String port = entity.getPort(); 124 | if (port != null) { 125 | stmt.bindString(3, port); 126 | } 127 | 128 | String type = entity.getType(); 129 | if (type != null) { 130 | stmt.bindString(4, type); 131 | } 132 | 133 | String link = entity.getLink(); 134 | if (link != null) { 135 | stmt.bindString(5, link); 136 | } 137 | 138 | String name = entity.getName(); 139 | if (name != null) { 140 | stmt.bindString(6, name); 141 | } 142 | 143 | String user = entity.getUser(); 144 | if (user != null) { 145 | stmt.bindString(7, user); 146 | } 147 | 148 | String time = entity.getTime(); 149 | if (time != null) { 150 | stmt.bindString(8, time); 151 | } 152 | } 153 | 154 | @Override 155 | public Long readKey(Cursor cursor, int offset) { 156 | return cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0); 157 | } 158 | 159 | @Override 160 | public SuiDao readEntity(Cursor cursor, int offset) { 161 | SuiDao entity = new SuiDao( // 162 | cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0), // id 163 | cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1), // ip 164 | cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2), // port 165 | cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3), // type 166 | cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4), // link 167 | cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5), // name 168 | cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6), // user 169 | cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7) // time 170 | ); 171 | return entity; 172 | } 173 | 174 | @Override 175 | public void readEntity(Cursor cursor, SuiDao entity, int offset) { 176 | entity.setId(cursor.isNull(offset + 0) ? null : cursor.getLong(offset + 0)); 177 | entity.setIp(cursor.isNull(offset + 1) ? null : cursor.getString(offset + 1)); 178 | entity.setPort(cursor.isNull(offset + 2) ? null : cursor.getString(offset + 2)); 179 | entity.setType(cursor.isNull(offset + 3) ? null : cursor.getString(offset + 3)); 180 | entity.setLink(cursor.isNull(offset + 4) ? null : cursor.getString(offset + 4)); 181 | entity.setName(cursor.isNull(offset + 5) ? null : cursor.getString(offset + 5)); 182 | entity.setUser(cursor.isNull(offset + 6) ? null : cursor.getString(offset + 6)); 183 | entity.setTime(cursor.isNull(offset + 7) ? null : cursor.getString(offset + 7)); 184 | } 185 | 186 | @Override 187 | protected final Long updateKeyAfterInsert(SuiDao entity, long rowId) { 188 | entity.setId(rowId); 189 | return rowId; 190 | } 191 | 192 | @Override 193 | public Long getKey(SuiDao entity) { 194 | if(entity != null) { 195 | return entity.getId(); 196 | } else { 197 | return null; 198 | } 199 | } 200 | 201 | @Override 202 | public boolean hasKey(SuiDao entity) { 203 | return entity.getId() != null; 204 | } 205 | 206 | @Override 207 | protected final boolean isEntityUpdateable() { 208 | return true; 209 | } 210 | 211 | } 212 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/knife_net_Activity.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.app.Activity; 4 | import android.content.res.AssetManager; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.CheckBox; 9 | import android.widget.EditText; 10 | import android.widget.ImageView; 11 | import android.widget.LinearLayout; 12 | import android.widget.RadioButton; 13 | import android.widget.RadioGroup; 14 | import android.widget.Spinner; 15 | import android.widget.Toast; 16 | 17 | import com.activity.greendao.DBSuiDaoHelper; 18 | import com.activity.greendao.HttpUtil; 19 | import com.activity.greendao.SuiDao; 20 | import com.frpc.R; 21 | 22 | import java.io.File; 23 | import java.io.FileOutputStream; 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.text.SimpleDateFormat; 27 | import java.util.Date; 28 | 29 | 30 | public class knife_net_Activity extends Activity { 31 | private RadioGroup typeRG; 32 | private EditText nameEx; 33 | private EditText IPEx; 34 | private EditText portEx; 35 | private EditText remote_portEx; 36 | private EditText domain_nameEx; 37 | private CheckBox compressCB; 38 | private CheckBox secretCB; 39 | private Button saveBt; 40 | private LinearLayout yumingLy;//域名 41 | private LinearLayout domain_type_layoutLy;//域名类型 42 | private LinearLayout remote_port_ly; 43 | private RadioGroup access_type_rg; 44 | private String suidaoType; 45 | private String name;//隧道名称 46 | private String local_ip; 47 | private String local_port; 48 | private String remote_port; 49 | private String domain_name; 50 | private String is_secret; 51 | private String is_compress; 52 | 53 | @Override 54 | protected void onCreate(Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | setContentView(R.layout.activity_knife_net); 57 | initView(); 58 | initEvent(); 59 | } 60 | 61 | private void initView(){ 62 | typeRG = findViewById(R.id.type_radio); 63 | nameEx = findViewById(R.id.name); 64 | IPEx = findViewById(R.id.accesss_ip); 65 | portEx = findViewById(R.id.port); 66 | remote_portEx = findViewById(R.id.remote_port); 67 | access_type_rg = findViewById(R.id.access_type_rg); 68 | yumingLy = findViewById(R.id.yuming); 69 | domain_type_layoutLy = findViewById(R.id.domain_type_layout); 70 | remote_port_ly = findViewById(R.id.remote_port_ly); 71 | domain_nameEx = findViewById(R.id.domain_name); 72 | compressCB = findViewById(R.id.compress); 73 | secretCB = findViewById(R.id.secret); 74 | saveBt = findViewById(R.id.save); 75 | 76 | } 77 | private void initEvent(){ 78 | yumingLy.setVisibility(View.GONE); 79 | domain_type_layoutLy.setVisibility(View.GONE); 80 | typeRG.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { 81 | @Override 82 | public void onCheckedChanged(RadioGroup group, int checkedId) { 83 | switch (checkedId){ 84 | case R.id.tcp: 85 | suidaoType = "tcp"; 86 | yumingLy.setVisibility(View.GONE); 87 | domain_type_layoutLy.setVisibility(View.GONE); 88 | remote_port_ly.setVisibility(View.VISIBLE); 89 | break; 90 | case R.id.udp: 91 | suidaoType = "udp"; 92 | yumingLy.setVisibility(View.GONE); 93 | domain_type_layoutLy.setVisibility(View.GONE); 94 | remote_port_ly.setVisibility(View.VISIBLE); 95 | break; 96 | case R.id.http: 97 | suidaoType = "http"; 98 | yumingLy.setVisibility(View.VISIBLE); 99 | domain_type_layoutLy.setVisibility(View.VISIBLE); 100 | remote_port_ly.setVisibility(View.GONE); 101 | break; 102 | case R.id.https: 103 | suidaoType = "https"; 104 | yumingLy.setVisibility(View.VISIBLE); 105 | domain_type_layoutLy.setVisibility(View.VISIBLE); 106 | remote_port_ly.setVisibility(View.GONE); 107 | break; 108 | default: 109 | break; 110 | } 111 | } 112 | }); 113 | 114 | 115 | 116 | saveBt.setOnClickListener(new View.OnClickListener() { 117 | @Override 118 | public void onClick(View v) { 119 | if(nameEx.getText().length()>1 && IPEx.getText().length()>1 && portEx.getText().length()>1 ){ 120 | if(typeRG.getCheckedRadioButtonId() == R.id.tcp || typeRG.getCheckedRadioButtonId() == R.id.udp){ 121 | if(remote_portEx.getText().length()<2){ 122 | Toast.makeText(knife_net_Activity.this,"请完善信息",Toast.LENGTH_SHORT).show(); 123 | return; 124 | } 125 | }else{ 126 | if(domain_nameEx.getText().length()<2){ 127 | Toast.makeText(knife_net_Activity.this,"请完善信息",Toast.LENGTH_SHORT).show(); 128 | return; 129 | } 130 | } 131 | 132 | SuidaoInfo();//总和隧道信息 133 | copyToSD(MyApplication.FILENAME);//写入数据库 134 | addsuidao(suidaoType,local_ip,local_port,remote_port,name,domain_name);//写入配置文件 135 | HttpUtil.frpreload(); 136 | finish(); 137 | }else{ 138 | Toast.makeText(knife_net_Activity.this,"请完善信息",Toast.LENGTH_SHORT).show(); 139 | } 140 | 141 | } 142 | }); 143 | } 144 | 145 | private void SuidaoInfo(){ 146 | switch (typeRG.getCheckedRadioButtonId()){ 147 | case R.id.tcp: 148 | suidaoType = "tcp"; 149 | break; 150 | case R.id.udp: 151 | suidaoType = "udp"; 152 | break; 153 | case R.id.http: 154 | suidaoType = "http"; 155 | break; 156 | case R.id.https: 157 | suidaoType = "https"; 158 | break; 159 | default: 160 | suidaoType = "tcp"; 161 | break; 162 | } 163 | name = nameEx.getText().toString(); 164 | local_ip = IPEx.getText().toString(); 165 | local_port = portEx.getText().toString(); 166 | remote_port = remote_portEx.getText().toString(); 167 | domain_name = domain_nameEx.getText().toString(); 168 | if(compressCB.isChecked()){ 169 | is_compress = "true"; 170 | }else{ 171 | is_compress = "false"; 172 | } 173 | if(secretCB.isChecked()){ 174 | is_secret = "true"; 175 | }else{ 176 | is_secret = "false"; 177 | } 178 | } 179 | private void copyToSD(String dbName) { 180 | InputStream in = null; 181 | FileOutputStream out = null; 182 | 183 | //判断如果数据库已经拷贝成功,不需要再次拷贝 184 | File file = new File(this.getExternalFilesDir(null), dbName); 185 | if (!file.exists()) { 186 | try { 187 | file.createNewFile(); 188 | AssetManager assets = getAssets(); 189 | //2.读取数据资源 190 | in = assets.open(dbName); 191 | out = new FileOutputStream(file); 192 | //3.读写操作 193 | byte[] b = new byte[1024];//缓冲区域 194 | int len = -1; //保存读取的长度 195 | while ((len = in.read(b)) != -1) { 196 | out.write(b, 0, len); 197 | } 198 | } catch (IOException e) { 199 | e.printStackTrace(); 200 | } 201 | } 202 | 203 | try { 204 | out = new FileOutputStream(file,true); 205 | if(suidaoType.equals("tcp")){ 206 | String socket_name = "["+name+"]\n"; 207 | String socket_type = "type = "+suidaoType+"\r\n"; 208 | String socket_ip = "local_ip = "+ local_ip + "\r\n"; 209 | String socket_port = "local_port = " + local_port + "\r\n"; 210 | String socket_remoteport = "remote_port = " + remote_port+ "\r\n"; 211 | String use_encryption = "use_encryption = "+is_secret+"\r\n"; 212 | String use_compression = "use_compression = "+is_compress+"\r\n"; 213 | out.write(socket_name.getBytes()); 214 | out.write(socket_type.getBytes()); 215 | out.write(socket_ip.getBytes()); 216 | out.write(socket_port.getBytes()); 217 | out.write(socket_remoteport.getBytes()); 218 | out.write(use_encryption.getBytes()); 219 | out.write(use_compression.getBytes()); 220 | }else if(suidaoType.equals("http")){ 221 | if(access_type_rg.getCheckedRadioButtonId() == R.id.custom){ 222 | String socket_name = "["+name+"]\n"; 223 | String socket_type = "type = "+suidaoType+"\r\n"; 224 | String socket_ip = "local_ip = "+ local_ip + "\r\n"; 225 | String socket_port = "local_port = " + local_port + "\r\n"; 226 | String socket_subdomain = "custom_domains = " + domain_name+ "\r\n"; 227 | String use_encryption = "use_encryption = "+is_secret+"\r\n"; 228 | String use_compression = "use_compression = "+is_compress+"\r\n"; 229 | out.write(socket_name.getBytes()); 230 | out.write(socket_type.getBytes()); 231 | out.write(socket_ip.getBytes()); 232 | out.write(socket_port.getBytes()); 233 | out.write(socket_subdomain.getBytes()); 234 | out.write(use_encryption.getBytes()); 235 | out.write(use_compression.getBytes()); 236 | }else{ 237 | String socket_name = "["+name+"]\n"; 238 | String socket_type = "type = "+suidaoType+"\r\n"; 239 | String socket_ip = "local_ip = "+ local_ip + "\r\n"; 240 | String socket_port = "local_port = " + local_port + "\r\n"; 241 | String socket_subdomain = "subdomain = " + domain_name+ "\r\n"; 242 | String use_encryption = "use_encryption = "+is_secret+"\r\n"; 243 | String use_compression = "use_compression = "+is_compress+"\r\n"; 244 | out.write(socket_name.getBytes()); 245 | out.write(socket_type.getBytes()); 246 | out.write(socket_ip.getBytes()); 247 | out.write(socket_port.getBytes()); 248 | out.write(socket_subdomain.getBytes()); 249 | out.write(use_encryption.getBytes()); 250 | out.write(use_compression.getBytes()); 251 | } 252 | 253 | }else if(suidaoType.equals("udp")){ 254 | String socket_name = "["+name+"]\n"; 255 | String socket_type = "type = "+suidaoType+"\r\n"; 256 | String socket_ip = "local_ip = "+ local_ip + "\r\n"; 257 | String socket_port = "local_port = " + local_port + "\r\n"; 258 | String use_encryption = "use_encryption = "+is_secret+"\r\n"; 259 | String use_compression = "use_compression = "+is_compress+"\r\n"; 260 | out.write(socket_name.getBytes()); 261 | out.write(socket_type.getBytes()); 262 | out.write(socket_ip.getBytes()); 263 | out.write(socket_port.getBytes()); 264 | out.write(use_encryption.getBytes()); 265 | out.write(use_compression.getBytes()); 266 | }else if(suidaoType.equals("https")){ 267 | if(access_type_rg.getCheckedRadioButtonId() == R.id.custom){ 268 | String socket_name = "["+name+"]\n"; 269 | String socket_type = "type = "+suidaoType+"\r\n"; 270 | String socket_ip = "local_ip = "+ local_ip + "\r\n"; 271 | String socket_port = "local_port = " + local_port + "\r\n"; 272 | String socket_subdomain = "custom_domains = " + domain_name+ "\r\n"; 273 | String use_encryption = "use_encryption = "+is_secret+"\r\n"; 274 | String use_compression = "use_compression = "+is_compress+"\r\n"; 275 | out.write(socket_name.getBytes()); 276 | out.write(socket_type.getBytes()); 277 | out.write(socket_ip.getBytes()); 278 | out.write(socket_port.getBytes()); 279 | out.write(socket_subdomain.getBytes()); 280 | out.write(use_encryption.getBytes()); 281 | out.write(use_compression.getBytes()); 282 | }else{ 283 | String socket_name = "["+name+"]\n"; 284 | String socket_type = "type = "+suidaoType+"\r\n"; 285 | String socket_ip = "local_ip = "+ local_ip + "\r\n"; 286 | String socket_port = "local_port = " + local_port + "\r\n"; 287 | String socket_subdomain = "subdomain = " + domain_name+ "\r\n"; 288 | String use_encryption = "use_encryption = "+is_secret+"\r\n"; 289 | String use_compression = "use_compression = "+is_compress+"\r\n"; 290 | out.write(socket_name.getBytes()); 291 | out.write(socket_type.getBytes()); 292 | out.write(socket_ip.getBytes()); 293 | out.write(socket_port.getBytes()); 294 | out.write(socket_subdomain.getBytes()); 295 | out.write(use_encryption.getBytes()); 296 | out.write(use_compression.getBytes()); 297 | } 298 | } 299 | 300 | } catch (IOException e) { 301 | e.printStackTrace(); 302 | } finally { 303 | try { 304 | if (in != null) in.close(); 305 | if (out != null) out.close(); 306 | } catch (IOException e) { 307 | e.printStackTrace(); 308 | } 309 | } 310 | } 311 | 312 | private void addsuidao(String user,String ip,String port,String type,String name,String link){ 313 | SuiDao temp = new SuiDao(); 314 | temp.setUser(user); 315 | temp.setIp(ip); 316 | temp.setPort(port); 317 | temp.setType(type); 318 | temp.setName(name); 319 | temp.setLink(link); 320 | temp.setTime(TimeToInvitation()); 321 | DBSuiDaoHelper.insertsuidao(temp); 322 | } 323 | 324 | public String TimeToInvitation(){ 325 | Date date = new Date(); 326 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); 327 | String invitation = sdf.format(date).toString(); 328 | return invitation; 329 | } 330 | 331 | } 332 | -------------------------------------------------------------------------------- /app/src/main/java/com/activity/knife_net_list.java: -------------------------------------------------------------------------------- 1 | package com.activity; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.AsyncTask; 6 | import android.os.Bundle; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.util.Log; 9 | import android.util.SparseBooleanArray; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.AdapterView; 14 | import android.widget.CheckBox; 15 | import android.widget.CompoundButton; 16 | import android.widget.TextView; 17 | 18 | import com.activity.greendao.DBSuiDaoHelper; 19 | import com.activity.greendao.HttpUtil; 20 | import com.activity.greendao.SuiDao; 21 | import com.frpc.R; 22 | 23 | import java.io.File; 24 | import java.util.List; 25 | 26 | import butterknife.Bind; 27 | import butterknife.ButterKnife; 28 | import butterknife.OnClick; 29 | import frpclib.Frpclib; 30 | 31 | public class knife_net_list extends BaseRecyclerViewSwipeRefreshActivity implements CompoundButton.OnCheckedChangeListener { 32 | @Bind(R.id.foot_bar) 33 | View footBarView; 34 | @Bind(R.id.edit) 35 | TextView editTv; 36 | @Bind(R.id.add) 37 | TextView addTv; 38 | @Bind(R.id.all_check) 39 | CheckBox mAllCheck; 40 | @Bind(R.id.start) 41 | TextView startTx; 42 | private List SuiDaolist; 43 | long back_button_click_time; 44 | boolean bBackButtonClick = false; 45 | private UserLoginTask mAuthTask = null; 46 | 47 | @Override 48 | protected void onCreate(Bundle savedInstanceState) { 49 | super.onCreate(savedInstanceState); 50 | setContentView((R.layout.activity_my_suidao)); 51 | ButterKnife.bind(this); 52 | mAllCheck.setOnCheckedChangeListener(this); 53 | } 54 | 55 | @Override 56 | protected void onResume() { 57 | super.onResume(); 58 | mList.clear(); 59 | requestData(); 60 | } 61 | 62 | @Override 63 | protected void initView() { 64 | super.initView(); 65 | Log.d("MyNewsActivity", "initView: " + getAdapter().getItemCount()); 66 | } 67 | 68 | @Override 69 | protected void setupSwipeRefreshLayout() { 70 | super.setupSwipeRefreshLayout(); 71 | mSwipeRefreshLayout.setEnabled(false); 72 | } 73 | 74 | @OnClick(R.id.start) 75 | public void start() { 76 | startTx.setText("已启动"); 77 | Log.d("frpc", "frpc启动"); 78 | frpStart(); 79 | } 80 | 81 | @OnClick(R.id.add) 82 | public void add() { 83 | Intent intent = new Intent(knife_net_list.this, knife_net_Activity.class); 84 | startActivity(intent); 85 | } 86 | 87 | public void onBackPressed() { 88 | 89 | if (false == bBackButtonClick) { 90 | 91 | back_button_click_time = System.currentTimeMillis(); 92 | bBackButtonClick = true; 93 | 94 | ResetBackButtonThread backButtonThread = new ResetBackButtonThread(); 95 | backButtonThread.start(); 96 | } else { 97 | super.onBackPressed(); 98 | } 99 | } 100 | 101 | class ResetBackButtonThread extends Thread { 102 | public void run() { 103 | try { 104 | 105 | Thread.sleep(3500); 106 | } catch (InterruptedException e) { 107 | e.printStackTrace(); 108 | } 109 | 110 | bBackButtonClick = false; 111 | } 112 | } 113 | 114 | @Override 115 | protected void requestData() { 116 | SuiDaolist = DBSuiDaoHelper.queryAll(); 117 | for (int i = 0; i < SuiDaolist.size(); i++) { 118 | if (SuiDaolist.get(i).getIp().equals("127.0.0.1")) { 119 | MyApplication.getDaoInstant().getSuiDaoDao().delete(SuiDaolist.get(i)); 120 | } 121 | } 122 | onRefreshSucceed(DBSuiDaoHelper.queryAll()); 123 | } 124 | 125 | @Override 126 | public ItemViewCreator configItemViewCreator() { 127 | return new ItemViewCreator() { 128 | @Override 129 | public View newContentView(LayoutInflater inflater, ViewGroup parent, int viewType) { 130 | return inflater.inflate(MySuiDaoViewHolder.LAYOUT_RES, parent, false); 131 | } 132 | 133 | @Override 134 | public IItemView newItemView(View view, int viewType) { 135 | return new MySuiDaoViewHolder(view); 136 | } 137 | }; 138 | } 139 | 140 | @Override 141 | public BaseRecyclerAdapter newAdapter() { 142 | return new SuiDaoAdapter(this, configItemViewCreator(), mList); 143 | } 144 | 145 | @Override 146 | public void onItemClick(AdapterView parent, View view, int position, long id) { 147 | // if (!footBarView.isShown()) { 148 | //// Intent intent = new Intent(this,NewsDetailActivity.class); 149 | //// intent.putExtra("fal", (Parcelable) mList.get(position)); 150 | //// startActivity(intent); 151 | // mList.get(position).is_read = 1; 152 | // getAdapter().notifyItemChanged(position); 153 | // ActivityUtils.startActivity(this, NewsDetailActivity.class,mList.get(position).id); 154 | // } 155 | 156 | } 157 | 158 | @Override 159 | protected void configRecyclerView() { 160 | mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST)); 161 | } 162 | 163 | @Override 164 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 165 | SuiDaoAdapter adapter = (SuiDaoAdapter) getAdapter(); 166 | adapter.selectAllOrNot(isChecked); 167 | 168 | } 169 | 170 | class SuiDaoAdapter extends BaseRecyclerAdapter { 171 | private int mode = 1; 172 | private SparseBooleanArray flags = new SparseBooleanArray(); 173 | 174 | public SuiDaoAdapter(Context context, ItemViewCreator creator, List list) { 175 | super(context, creator, list); 176 | } 177 | 178 | public void setMode(int m) { 179 | this.mode = m; 180 | notifyDataSetChanged(); 181 | } 182 | 183 | public void selectAllOrNot(boolean flag) { 184 | for (int i = 0; i < mList.size(); i++) { 185 | flags.put(i, flag); 186 | } 187 | notifyDataSetChanged(); 188 | } 189 | 190 | public void deleteSuiDao() { 191 | IniFile file = new IniFile(new File(knife_net_list.this.getExternalFilesDir(null), "config.ini")); 192 | for (int i = 0; i < flags.size(); i++) { 193 | if (flags.get(i)) { 194 | if (file.isexists()) { 195 | Long temp = mList.get(i).getId(); 196 | file.remove(DBSuiDaoHelper.quarySetionName(temp)); 197 | DBSuiDaoHelper.deletesuidao(temp); 198 | // Log.d("configini",(String) file.get("common").get("protocol")); 199 | // Log.d("configini",file.get("telnet_test").get("type").toString()); 200 | // 201 | // Log.d("configini",file.get("telnet_test").get("type").toString()); 202 | } 203 | } 204 | } 205 | file.save(); 206 | mList.clear(); 207 | requestData(); 208 | } 209 | 210 | @Override 211 | public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) { 212 | super.onBindViewHolder(holder, position); 213 | if (holder instanceof MySuiDaoViewHolder) { 214 | final MySuiDaoViewHolder holder1 = (MySuiDaoViewHolder) holder; 215 | if (mode == 2) { 216 | holder1.circleBox.setVisibility(View.VISIBLE); 217 | } else { 218 | holder1.circleBox.setVisibility(View.GONE); 219 | } 220 | holder1.circleBox.setImageResource(flags.get(position) ? R.mipmap.ic_launcher 221 | : R.mipmap.ic_launcher); 222 | holder1.circleBox.setOnClickListener(new View.OnClickListener() { 223 | @Override 224 | public void onClick(View v) { 225 | boolean flag = flags.get(position); 226 | flags.put(position, !flag); 227 | holder1.circleBox.setImageResource(!flag ? R.mipmap.ic_launcher 228 | : R.mipmap.ic_launcher); 229 | } 230 | }); 231 | } 232 | } 233 | } 234 | 235 | @OnClick(R.id.edit) 236 | public void edit() { 237 | if (footBarView.isShown()) { 238 | footBarView.setVisibility(View.GONE); 239 | editTv.setText("编辑"); 240 | ((SuiDaoAdapter) getAdapter()).setMode(1); 241 | } else { 242 | footBarView.setVisibility(View.VISIBLE); 243 | editTv.setText("取消"); 244 | ((SuiDaoAdapter) getAdapter()).setMode(2); 245 | ((SuiDaoAdapter) getAdapter()).selectAllOrNot(false); 246 | } 247 | } 248 | 249 | @OnClick(R.id.remove) 250 | public void remove() { 251 | SuiDaoAdapter adapter = (SuiDaoAdapter) getAdapter(); 252 | adapter.deleteSuiDao(); 253 | HttpUtil.frpreload(); 254 | footBarView.setVisibility(View.GONE); 255 | editTv.setText("编辑"); 256 | ((SuiDaoAdapter) getAdapter()).setMode(1); 257 | ((SuiDaoAdapter) getAdapter()).selectAllOrNot(false); 258 | } 259 | 260 | private void frpStart() { 261 | if (mAuthTask != null) { 262 | return; 263 | } 264 | mAuthTask = new UserLoginTask(getExternalFilesDir(null) + "/config.ini"); 265 | Log.e("path", getExternalFilesDir(null) + "/config.ini");//打印 266 | mAuthTask.execute((Void) null);//执行异步线程 267 | } 268 | 269 | public class UserLoginTask extends AsyncTask { 270 | 271 | private final String mConfigPath; 272 | 273 | UserLoginTask(String email) { 274 | mConfigPath = email; 275 | } 276 | 277 | @Override 278 | protected void onPreExecute() { 279 | 280 | super.onPreExecute(); 281 | } 282 | 283 | @Override 284 | protected Boolean doInBackground(Void... params) { 285 | try { 286 | Frpclib.run(mConfigPath); 287 | } catch (Throwable e) { 288 | if (e != null && e.getMessage() != null) { 289 | Log.e("throwable", e.getMessage() + ""); 290 | } 291 | 292 | } 293 | return true; 294 | } 295 | 296 | @Override 297 | protected void onPostExecute(final Boolean success) { 298 | //由于Frpclib.run(mConfigPath)该方法保持了长连接,所以这些方法都走不进去,只是摆设 299 | if (success) { 300 | } else { 301 | } 302 | mAuthTask = null; 303 | // finish(); 304 | } 305 | 306 | @Override 307 | protected void onCancelled() { 308 | //由于Frpclib.run(mConfigPath)该方法保持了长连接,所以这些方法都走不进去,只是摆设,但退出程序会走这个方法 309 | Log.e("onCancelled", "+++++++"); 310 | mAuthTask = null; 311 | } 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /app/src/main/res/anim/bottom_slide_enter.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/left_slide_exit_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/left_slip_enter_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/right_slide_enter_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/right_slip_exit_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/anim/top_slide_exit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /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/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFM880/frpc-Android/12d3dc849958eae64a42bb640b85048444bd1ee8/app/src/main/res/drawable/background.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/blue_line.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_bg_blue_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_bg_blue_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_bg_blue_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_bg_blue_unenabled.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_fang.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | android:shape="rectangle"> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/buttonstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/custom_radio_btn.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_must_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFM880/frpc-Android/12d3dc849958eae64a42bb640b85048444bd1ee8/app/src/main/res/drawable/ic_must_fill.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/radiogroup.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/start_app.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CFM880/frpc-Android/12d3dc849958eae64a42bb640b85048444bd1ee8/app/src/main/res/drawable/start_app.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/text_bg_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/text_bg_blue_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/text_bg_blue_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_knife_net.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 45 | 46 | 55 | 56 | 62 | 63 | 68 | 69 | 71 | 72 | 76 | 77 | 83 | 90 | 96 | 102 | 108 | 109 | 110 | 111 | 112 | 116 | 119 | 120 | 123 | 128 | 135 | 136 | 143 | 144 | 145 | 146 | 147 | 148 | 152 | 155 | 156 | 159 | 164 | 170 | 171 | 179 | 180 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 198 | 201 | 202 | 206 | 207 | 212 | 213 | 214 | 215 | 219 | 220 | 222 | 223 | 224 | 228 | 229 | 234 | 235 | 239 | 241 | 242 | 243 | 247 | 252 | 253 | 254 | 258 | 260 | 261 | 265 | 266 | 273 | 274 | 278 | 281 | 282 | 286 | 287 | 294 | 295 | 299 | 300 | 304 | 305 |