Android 开发中慎用静态变量
静态变量,在Java中是当类被卸载的时候会被清空,一般情况下被卸载的时候是JVM结束的时候。但是在Android这里,使用的是Dalvik虚拟机,每个应用启动的时候都会实例化一个虚拟机对象,应用结束的时候就会消失,然后静态变量就会被回收了。
常见场景
应用后台的时候,被回收了,当用户恢复应用的时候,静态变量就会被重置为空,就会出现问题.
没错,就是我
静态变量,在Java中是当类被卸载的时候会被清空,一般情况下被卸载的时候是JVM结束的时候。但是在Android这里,使用的是Dalvik虚拟机,每个应用启动的时候都会实例化一个虚拟机对象,应用结束的时候就会消失,然后静态变量就会被回收了。
应用后台的时候,被回收了,当用户恢复应用的时候,静态变量就会被重置为空,就会出现问题.
Custom States
作者详细介绍了怎么自定义按钮的状态(不是按钮状态的响应哦) 原来这东西还可以自定义的
为了XLog的使用方便,我决定把XLog提交到jcenter
参考了这篇文章
将自己的开源项目提交到JCENTER
这篇文章是可行的,需要修改的地方是把项目的build.gradle 修改为以下内容
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.2'
classpath 'com.github.dcendents:android-maven-plugin:1.2'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
需要把build:gradle:1.1.0 改为 1.1.2 ,因为1.1.0有bug,会出现错误
最近在写判断ListView是否可以滚动的代码,忽然发现一个函数
“canScrollList“
它的代码是这样的
/**
* Check if the items in the list can be scrolled in a certain direction.
*
* @param direction Negative to check scrolling up, positive to check
* scrolling down.
* @return true if the list can be scrolled in the specified direction,
* false otherwise.
* @see #scrollListBy(int)
*/
public boolean canScrollList(int direction) {
final int childCount = getChildCount();
if (childCount == 0) {
return false;
}
final int firstPosition = mFirstPosition;
final Rect listPadding = mListPadding;
if (direction > 0) {
final int lastBottom = getChildAt(childCount - 1).getBottom();
final int lastPosition = firstPosition + childCount;
return lastPosition < mItemCount || lastBottom > getHeight() - listPadding.bottom;
} else {
final int firstTop = getChildAt(0).getTop();
return firstPosition > 0 || firstTop < listPadding.top;
}
}
看到这里大家都明白了,这个代码虽然是API19才能用,但是是可以提取出来兼容到低版本的
于是,我就做了这样一个兼容的方法
/**
* 判断是否可以滚动的兼容函数
* @param listView
* @param direction 方向,大于0就是向下的方向
* @return
*/
public static boolean canScrollList(AbsListView listView, int direction) {
if (Build.VERSION.SDK_INT >= 19) return listView.canScrollList(direction);
else {
final int childCount = listView.getChildCount();
if (childCount == 0) {//列表中没有一个子对象的时候,当然是不能滚动啦
return false;
}
final int firstPosition = listView.getFirstVisiblePosition();
if (direction > 0) {//判断方向,大于零的就是表示判断能否往下滚动
final int lastBottom = listView.getChildAt(childCount - 1).getBottom();
final int lastPosition = firstPosition + childCount;
//最右一个item的位置小宇总位数的时候可以滚动
// 最后一个item的底部高度如果比减去padding之后的
// listview底部还要高的那就是在屏幕外,就是可以滚动的
return lastPosition < listView.getCount() || lastBottom > listView.getHeight() - listView.getListPaddingBottom();
} else {
final int firstTop = listView.getChildAt(0).getTop();
return firstPosition > 0 || firstTop < listView.getListPaddingTop();
}
}
}
注意这个东西只能在item 等高的时候判断
很多时候我们希望给我们的APK命名为:
"appname_buildtype_buildVersion_versionName_buildingTime.apk"
这样格式方便我们给运营让我们可以一眼识别这个是什么时候发布的APK,但是这样的东西如果让我们手动编写肯定会非常麻烦,gradle-packer-plugin就是用来干这个事情的,当然还支持多渠道打包。
应用的使用方法github上都有介绍,在这里我说说这个插件如何放到run上,让我们可以一键打包


XLog 是一个帮助Android开发打印的调试信息的工具类
Log.i(TAG,print string);
我们需要构建TAG,并且还要防止 print string 为null 这种情况
在xlog,不需要考虑这些
XLog.i(print object);
不需要tag ,自动默认的tag就是当前调用的类和行数,打印的是object.toString()内容,不需要把基本类型弄成
string,自动判断是否为空,为空时,会打印报空的问题。
XLog.setLogState(false);
关闭所有的debug
也可以单独关闭每一个
XLog.allowI = false;
xutils 一个开源框架,xLog参考了其中LogUtils大部分的思路,改进了流程
A helper to use system gallery or camera take Photo
used code
fix some problem
CropHelper cropHelper = new CropHelper(this);
cropHelper.setCropParams(new CropParams());
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
cropHelper.handleResult(requestCode,resultCode,data);
}
cropHelper.setCropListener(new CropListener() {
@Override
public void onPhotoCropped(Uri uri) {
Log.d(TAG, "Crop Uri in path: " + uri.getPath());
Toast.makeText(TestActivity.this, "Photo cropped!", Toast.LENGTH_LONG).show();
mImageView.setImageBitmap(cropHelper.getCropBitmap());
}
@Override
public void onCropCancel() {
Toast.makeText(TestActivity.this,"cancel",Toast.LENGTH_SHORT).show();
}
@Override
public void onCropFailed(String message) {
Toast.makeText(TestActivity.this,message+"",Toast.LENGTH_SHORT).show();
}
});
最近在做分享上一下事情,想遵守Android Design 规范做一个分享的菜单但是遇到一些问题
1. Android上ShareProvider 不支持捕获分享(也许是我没找到),于是就不能针对某个分享项做优化
2. 抛弃ShareProvider使用,单纯的使用Android的菜单分享选项,这样虽然解决了不能捕获的问题,但是也是带了一个问题,就是无法用比较少的代码来完成分享。(发现弹出对话框的分享方式是最简单省事的)
最后权衡了知识和利弊之后只能做个简单的分享类,在CreateMenu 的时候addSubMenu ,
然后在OnMenuOptionSelected() handle 操作就可以了。
代码如下
package com.changheng.app.ui;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import com.changheng.app.R;
import cn.sharesdk.framework.Platform;
import cn.sharesdk.framework.ShareSDK;
import cn.sharesdk.wechat.favorite.WechatFavorite;
import cn.sharesdk.wechat.friends.Wechat;
import cn.sharesdk.wechat.moments.WechatMoments;
/**
* Created With Android Studio
* User 47
* Date 2015/2/13
* Time 14:29
* 分享助手
*/
public class ShareMenuHelper {
private Context mContext;
private SubMenu mSubMenu;
private static ShareItem[] SHARE_ITEM = {
new ShareItem(R.drawable.logo_wechat,
R.string.wechat,
ShareSDK.getPlatform(Wechat.NAME)),
new ShareItem(R.drawable.logo_wechatfavorite,
R.string.wechatfavorite,
ShareSDK.getPlatform(WechatFavorite.NAME)),
new ShareItem(R.drawable.logo_wechatmoments,
R.string.wechatmoments,
ShareSDK.getPlatform(WechatMoments.NAME))
};
public ShareMenuHelper(Context context,SubMenu subMenu){
mContext = context;
mSubMenu = subMenu;
}
public void init(){
int i = 0;
for (ShareItem shareItem : SHARE_ITEM){
SubMenu subMenu = mSubMenu.addSubMenu(
Menu.NONE,shareItem.id = i++,Menu.NONE,
shareItem.titleRes);
subMenu.setIcon(shareItem.iconId);
}
}
public void handlerMenuSelected(MenuItem menuItem, Platform.ShareParams shareParams){
int id = menuItem.getItemId();
for (ShareItem shareItem : SHARE_ITEM) {
if (id == shareItem.id) doShare(shareItem,shareParams);
}
}
private void doShare(ShareItem shareItem, Platform.ShareParams shareParams) {
shareItem.platform.share(shareParams);
}
public static class ShareItem{
public int id;
public int iconId;
public int titleRes;
public Platform platform;
public ShareItem( int iconId, int titleRes, Platform platform) {
this.iconId = iconId;
this.titleRes = titleRes;
this.platform = platform;
}
}
}
这个东西主要比较纠结的地方在于,如何获取submenu。找了好久才发现代码是这样的
menuItem.getSubMenu();
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:background="?attr/colorPrimary"
app:titleTextAppearance="@style/MyTitleTextStyle"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"/>
about apptheme
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
more
这个使用的主题必须要和Actionbar使用的主题对应,比如的我用的Light.DarkAction.那么这个主题必须也要是这个,不然会连带ActionBar那边的按钮也会出现问题
about titleTextAppearance
app:titleTextAppearance="@style/MyTitleTextStyle"
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#FF3333</item>
<!-- darker variant for the status bar and contextual app bars -->
<item name="colorPrimaryDark">#FF3333</item>
<!-- theme UI controls like checkboxes and text fields -->
<item name="colorAccent">#FFF</item>
</style>
<!--<style name="pallte_btn">-->
<!--<item name="android:layout_width">60dp</item>-->
<!--<item name="android:layout_height">60dp</item>-->
<!--</style>-->
<!--<style name="tabStyle">-->
<!--</style>-->
<style name="TestTheme" parent="AppTheme">
<item name="actionBarStyle">@style/actionBarStyle</item>
<item name="actionMenuTextAppearance">@style/myActionMenuTextAppearance</item>
</style>
<style name="myActionMenuTextAppearance" parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Menu">
<item name="android:textSize">20sp</item>
<item name="android:shadowRadius">4</item>
<item name="android:shadowColor">#000</item>
<item name="android:shadowDx">2</item>
<item name="android:shadowDy">2</item>
</style>
<style name="actionBarStyle" parent="@style/Widget.AppCompat.Light.ActionBar.Solid">
<item name="titleTextStyle">@style/MyTitleTextStyle</item>
</style>
<style name="MyTitleTextStyle" parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
<item name="android:shadowRadius">4</item>
<item name="android:shadowColor">#000</item>
<item name="android:shadowDx">2</item>
<item name="android:shadowDy">2</item>
</style>
项目已经上线,有用户反馈说我们的程序有bug。同时,产品狗扔下了两个发布时间不一样的需求修复,拍照功能和美颜功能,拍照功能要下一版本发布,美颜功能要下下版本发布
如何修复Bug同时,不影响新功能的开发,并且把两次的需求隔离开来.
项目初始化的时候我们新建三个分支,分别是release ,develop和master。同时,项目发布的时候我们要建立tag,通过tag我们找回对应版本的代码。
保存可以发布的或者已经发布的代码分支
保存着开发好测试过的代码,每次develop上开发好的功能都可以合并到master分支
我们的开发分支,开发过程中分享的代码
有这三个分支的前提下我们就可以从容的应对需求了。
从v1.0.0(假设出现问题的就是v1.0.0版本) tag 中 checkout出新的分支,hotfix_xxx, 修复好之后直接合并回去发布
直接在develop上开发,开发完成后合并到master上,测试过后就可以合并到release上了
从master中新建一个分支,叫feature_pretty ,在这个分支上同步开发,功能要上线的时候再合并到release分支上
这样就比较好的方式解决了上面这个问题了
git clone https://github.com/hangox/git-use-demo.git
移动到项目的地址
cd /Users/hangox/Desktop/AndroidWorkspace/GitUseDemo
再敲入
git init
这个时候敲入git status
就会看到
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
.idea/
app/
build.gradle
gradle.properties
gradle/
gradlew
gradlew.bat
settings.gradle
nothing added to commit but untracked files present (use "git add" to track)
这是时候就会表示成功了
如果不设置代码提交者信息当然也可以提交,但是谁不希望提交上去的代码有个有个牛逼的名字呢😈
设置名字:
git config user.name "NiuBi"
设置邮箱:(出问题了人家可以找到你😂)
git config user.email "hangox.liang@gmail.com"
如果你想所有项目都是应用这个名字和邮箱只需要加上参数-- global
比如:
git config --global user.name "NiuBi"
git config user.email "hangox.liang@gmail.com"
要想查看现在的config信息可以这样
git config --list
你就会看到
user.name=47
user.email=liang.hanguang93@gmail.com
core.autocrlf=input
core.excludesfile=/Users/hangox/.gitignore_global
core.ignorecase=false
difftool.sourcetree.cmd=opendiff "$LOCAL" "$REMOTE"
difftool.sourcetree.path=
mergetool.sourcetree.cmd=/Applications/SourceTree.app/Contents/Resources/opendiff-w.sh "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED"
mergetool.sourcetree.trustexitcode=true
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
core.precomposeunicode=true
user.name=NiuBi
如果你发现了两个user.name和user.email ,这是其中一个是global的,一个是项目的而已
当然git config
还有很多可以设置的,包括合并工具什么的,自己挖掘吧!
.gitignore 就好像它的名字一样
告诉 git忽略一些不需要的文件
项目文件结构是这样的
.
├── GitUseDemo.iml
├── app
│ ├── app.iml
│ ├── build
│ ├── build.gradle
│ ├── libs
│ ├── proguard-rules.pro
│ └── src
├── build
│ └── generated
├── build.gradle
├── gradle
│ └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle
我想要忽略build文件夹,.iml 结尾文件和local.properties
#忽略build 文件夹
/build
#忽略local.properties 文件
local.properties
#忽略.xml 结尾的文件,使用正则
*.iml
#不忽略GitUseDemo.xml,这里只是为了演示,其实应该忽略的
!GitUseDemo.iml
#gradle 缓存文件,不需要提交
.gradle
#idea 的配置文件,不需要提交
.idea
这个时候我们敲入git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
GitUseDemo.iml
app/
build.gradle
gradle.properties
gradle/
gradlew
gradlew.bat
settings.gradle
nothing added to commit but untracked files present (use "git add" to track)
可以看到不需要的东西都被忽略了
git add .
##去掉--cached就是同时删除文件
git rm --cached build.gradle
添加后缀为.gradle的文件
git add *.gradle
简单的提交方法
git commit -m 'first commit'
如果你写的东西比较长,可以这样
git commit
这样将会出现一个vim 编辑器来编写,像这样

以上我们做的都这是发生在本地(这也是为什么Git快的原因了),如果需要和小伙伴们合作的话,就需要推送到远端了。
如果你是clone下来的可以跳过以下这一步了
# 添加远端,origin 只是一个名字而已,只是默认都是叫这个
git remote add origin https://github.com/hangox/git-use-demo.git
# 查看远端信息
git remote -v
# pull 其实是包含了两步操作,从remote 中fetch(拿来)代码,和当前分支(也就是master)
# merge(合并),如果你们公司使用的是rebase的话,就不能这样干了。但是大部分情况下都是
# 使用merge的
git pull origin master
# git push <remote-name> <remote-branch-name>
git push origin master
等待片刻,代码就上传完毕,你就可以和小伙伴们愉快的玩耍了O(∩_∩)O~~
没有100%的安全,我们做的只是提高破解的成本,当成本比破解得到的金钱高的时候,自然没有人愿意破解了
我们的接口协议被破,破解者通过程序直接刷我们的新增用户,通过邀请机制获取积分。
如何保证和接口通信的就是我们的客户端,而不是别人,或者更深的一层,如何保证客户端和服务器通信的就是彼此,没有第三方。
加上数据完整性的校验签名(前提是只有客户端才能生成这个检验签名)
使用加密算法(前提是只有客户端才能生成校验签名)
能做到以上两点,网络通信就没有基本问题了。
但是,以上两点都有个非常重要的前提,也是一个非常困难的前提
只有客户端能生成一些东西
加入加密过的时间戳(前提是只有客户端才能生成这个时间戳)
Java是非常容易反编译的语言,即使混淆了,一些常量也是无法混淆的,所以当反编译的时候非常容易通过搜索来找到对应的常量。但是C不一样,C写的东西反编译后是汇编代码,汇编代码并不是多少人可以看得懂的,一定程度上提高了破解的成本。
Android的so库,如果不对使用者进行校验,使用的人可以新建一个一样包名的应用,然后调用我们的so库,一样可以知道对应的输入输出。我们这里在C库里做了对签名的校验,如果发现签名不是我们的,就会用另外一条密钥对数据进行加密。
要想网络协议不被破解,App必须要能够有一个黑匣子一样的东西,让人无法破解,最常用的办法就是把安全逻辑的代码写入到C库中,并对C库进行校验。
最近做个项目,有一个按钮,有下载,安装,点击,安装中,下载中和使用这六个不同的状态,每个状态对应的按钮颜色也是不一样的,加上按下去的变色,大概有12种状态。
其实第二种和第一种的本质区别就是把所有的状态的文件写到一个xml里,但是更优雅,果断使用第二种
就这两个步骤,非常简单
<declare-styleable name="ColorStateButton">
<attr name="state_download" format="boolean"/>
<attr name="state_downloading" format="boolean"/>
<attr name="state_install" format="boolean"/>
<attr name="state_installing" format="boolean"/>
<attr name="state_open" format="boolean"/>
</declare-styleable>
其实我想试试可不可以直接用数字类型的,当时时间比较紧急,就没试了,有兴趣的可以试试
@Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mColorStatus != null && mColorStatus.getMergeState() != null && isEnabled()) {
final int[] drawableStatus = super.onCreateDrawableState(extraSpace + mColorStatus.getMergeState().length);//告诉系统你要添加拓展的状态数量
//告诉系统你要合并的状态
mergeDrawableStates(drawableStatus, mColorStatus.getMergeState());
// printList(drawableStatus);
return drawableStatus;
}¡
return super.onCreateDrawableState(extraSpace);
}
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- 需要下载状态-->
<item
android:drawable="@drawable/ic_color_button_download_normal"
android:state_pressed="false"
app:state_download="true"/>
<item
android:drawable="@drawable/ic_color_button_download_pressed"
android:state_pressed="true"
app:state_download="true"/>
<!--需要安装状态-->
<item
android:drawable="@drawable/ic_color_button_install"
android:state_pressed="false"
app:state_install="true"/>
<item
android:drawable="@drawable/ic_color_button_install_pressed"
android:state_pressed="true"
app:state_install="true"/>
<!--按钮不可用-->
<item
android:drawable="@drawable/ic_color_button_disable"
android:state_enabled="false"/>
<item
android:drawable="@drawable/ic_color_button_normal_pressed"
android:state_pressed="true"/>
<!--正常状态-->
<item
android:drawable="@drawable/ic_color_button_normal"/>
</selector>
package com.bingwish.bibao.ui.fragment;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.StringRes;
import android.util.AttributeSet;
import android.widget.Button;
import com.bingwish.bibao.R;
import com.hangox.xlog.XLog;
/**
* Created With Android Studio
* User hangox
* Date 16/1/11
* Time 下午8:58
* 下载显示按钮
*/
public class ColorStateButton extends Button {
public static final int[] STATUS_DOWNLOAD = {R.attr.state_download};
public static final int[] STATUS_INSTALL = {R.attr.state_install};
private ColorStatus mColorStatus = ColorStatus.DOWNLOAD;
public ColorStateButton(Context context) {
super(context);
init(context);
}
public ColorStateButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ColorStateButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ColorStateButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context) {
setColorState(ColorStatus.DOWNLOAD);
}
/***
* 设置颜色类型
*
* @param colorState
*/
public void setColorState(ColorStatus colorState) {
setText(colorState.getButtonTextRes());
switch (colorState) {
case DOWNLOADING:
case INSTALLING:
setEnabled(false);
return;
}
setEnabled(true);
mColorStatus = colorState;
refreshDrawableState();
invalidate();
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
if (mColorStatus != null && mColorStatus.getMergeState() != null && isEnabled()) {
final int[] drawableStatus = super.onCreateDrawableState(extraSpace + mColorStatus.getMergeState().length);
mergeDrawableStates(drawableStatus, mColorStatus.getMergeState());
// printList(drawableStatus);
return drawableStatus;
}
return super.onCreateDrawableState(extraSpace);
}
private void printList(int[] ints) {
StringBuilder builder = new StringBuilder();
for (int state : ints) {
builder.append(state).append("#");
}
XLog.i(builder.toString());
}
public enum ColorStatus {
DOWNLOAD(STATUS_DOWNLOAD, R.string.state_download),
CLICK(STATUS_INSTALL, R.string.state_click),
OPEN(null, R.string.state_open),
DOWNLOADING(null, R.string.state_downloading),
INSTALL(STATUS_INSTALL, R.string.state_install),
INSTALLING(null, R.string.state_installing);
int[] mergeState;
@StringRes
int mButtonTextRes;
ColorStatus(int[] statusDownload, @StringRes int buttonTextRes) {
mergeState = statusDownload;
mButtonTextRes = buttonTextRes;
}
public int getButtonTextRes() {
return mButtonTextRes;
}
public int[] getMergeState() {
return mergeState;
}
}
}
为了应对日益强大的防火墙,大部分人开始专用ShadowSock 作为代理,但是ShadowSock并不支持http 代理,而很多软件非常依赖于http 代理,比如Android SDK ,终端等,所以我们需要Privoxy
brew install proxivy
如果你看到这个了

表示你已经安装成功了
配置其实很简单,上面的截图已经告诉你配置文件在哪里,就是/usr/local/etc/privoxy/的config 文件
敲入
vi /usr/local/etc/privoxy/config
在最下面加入
# 监听端口为8118,如果需要其他设备访问,则需要将ip改为路由器的IP 192.168.1.1 或 0.0.0.0 或者直接 :8118
listen-address 0.0.0.0:8118 #如果要使用默认配置,可以不需要这行
forward-socks5 / 127.0.0.1:1080 .
#forward-socks5 表示监听sock5 ,127.0.0.1:1080是本地shadowsock 地址, "." 表示监听所有的URL
配置完成后如图

还记得安装成功后的截图不?
这是直接运行,但是关机后就没了
privoxy /usr/local/etc/privoxy/config
下面是加入系统启动项
ln -sfv /usr/local/opt/privoxy/*.plist ~/Library/LaunchAgents
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.privoxy.plist
如果你选择了其中一个敲入,并没有任何提示,那就是表示成功了
ps aux | grep privoxy
截图如下:

netstat -an | grep 8118
结果如下:

这样就是表示正在监听8118端口了
uname -a
#下载
scp -r -P {SSH端口号} {用户名}@{主机IP地址}:{主机文件目录} {本地文件目录}
#上传
scp -r -P {SSH端口号} {本地文件目录} {用户名}@{主机IP地址}:{主机文件目录}
ip addr show
sudo chkconfig [服务名] on
sudo chkconfig docker on
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --zone=public --remove-port=19999/tcp --permanent
修改/etc/selinux/config 文件
将SELINUX=enforcing改为SELINUX=disabled
不要直接修改/etc/profile 文件,把需要执行的代码加入到xx.sh 文件中,移动到/etc/profile.d/中就好了