浏览代码

看板异常奔溃时直接重启看板盒子,升级版本2.21

weizhengliang 2 年之前
父节点
当前提交
9399d9bec2

+ 15 - 2
app/build.gradle

@@ -14,10 +14,12 @@ android {
     buildToolsVersion "30.0.2"
     defaultConfig {
         applicationId "com.example.informationkanban"
-        minSdkVersion 15
+        minSdkVersion 19
         targetSdkVersion 30
         versionCode 1
-        versionName "2.1"
+        versionName "2.21"
+        multiDexEnabled true
+
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
     buildTypes {
@@ -76,4 +78,15 @@ dependencies {
 
     //eventbus
     implementation 'org.greenrobot:eventbus:3.0.0'
+
+    //使用xCrash捕获异常
+    implementation 'com.iqiyi.xcrash:xcrash-android-lib:3.0.0'
+
+    implementation 'com.squareup.okhttp3:okhttp:3.14.7'
+    implementation 'com.squareup.okio:okio:1.17.5'
+
+    /**
+     *   突破方法数限制
+     */
+    compile 'com.android.support:multidex:1.0.2'
 }

+ 2 - 2
app/src/main/AndroidManifest.xml

@@ -25,14 +25,14 @@
     <application
         android:name=".application.MyApplication"
         android:allowBackup="true"
-        android:icon="@mipmap/app_logo"
+        android:icon="@drawable/ic_app_launch"
         android:label="@string/app_name"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
         android:networkSecurityConfig="@xml/network_security_config"
         android:theme="@style/AppTheme">
         <activity android:name=".InitActivity">
-            <intent-filter>
+            <intent-filter android:priority="10">
                 <action android:name="android.intent.action.MAIN" />
 
                 <category android:name="android.intent.category.LAUNCHER" />

+ 9 - 0
app/src/main/java/com/example/informationkanban/InitActivity.java

@@ -12,6 +12,8 @@ import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.RadioButton;
@@ -54,6 +56,13 @@ public class InitActivity extends AppCompatActivity {
         LocaleMangerUtils.setApplicationLanguageByIndex(this, languageId);
 
         super.onCreate(savedInstanceState);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
+                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
+                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
+                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+
         setContentView(R.layout.init_layout);
 
         /*if ("NULL".equals(NetFunctionConfig.getUUID())) {

+ 7 - 2
app/src/main/java/com/example/informationkanban/MainActivity.java

@@ -57,8 +57,13 @@ public class MainActivity extends AppCompatActivity {
         LocaleMangerUtils.setApplicationLanguageByIndex(this, languageId);
 
         super.onCreate(savedInstanceState);
-//        requestWindowFeature(Window.FEATURE_NO_TITLE);
-//        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
+                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
+                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
+                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+
         setContentView(R.layout.activity_main);
         init();
         //MAC = GetInformationUtils.getMacAddress(this);

+ 12 - 0
app/src/main/java/com/example/informationkanban/XwalkMainActivity.java

@@ -13,11 +13,14 @@ import android.text.format.Time;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
 import android.webkit.ValueCallback;
 import android.webkit.WebSettings;
 import android.widget.Toast;
 
 import com.example.informationkanban.utils.GetInformationUtils;
+import com.example.informationkanban.utils.LocaleMangerUtils;
 import com.example.informationkanban.utils.NetFunctionConfig;
 
 import org.xwalk.core.XWalkActivity;
@@ -37,8 +40,17 @@ public class XwalkMainActivity extends XWalkActivity {
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
+        int languageId = NetFunctionConfig.getLanguageId(this);
+        LocaleMangerUtils.setApplicationLanguageByIndex(this, languageId);
+
         super.onCreate(savedInstanceState);
 
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
+                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
+                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
+                WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+
         setContentView(R.layout.activity_xwalk_view);
         xwalkview = (XWalkView) findViewById(R.id.view_xwalkview);
 

+ 3 - 0
app/src/main/java/com/example/informationkanban/application/MyApplication.java

@@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
 
 import com.example.informationkanban.utils.LocaleMangerUtils;
 import com.example.informationkanban.utils.NetFunctionConfig;
+import com.example.informationkanban.utils.XCrashUtils;
 
 public class MyApplication extends Application {
 
@@ -17,6 +18,8 @@ public class MyApplication extends Application {
         super.onCreate();
         myApplication = this;
 
+        //xCrash catcher
+        new XCrashUtils().init(this);
     }
     public static MyApplication getInstance() {
         return myApplication;

+ 20 - 1
app/src/main/java/com/example/informationkanban/utils/AppUtil.java

@@ -10,6 +10,7 @@ import com.example.informationkanban.InitActivity;
 
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
@@ -113,7 +114,7 @@ public class AppUtil {
     public static void restartApp(Context context) {
         //重新启动app
         Intent mStartActivity = new Intent(context.getApplicationContext(), InitActivity.class);
-        mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
         int mPendingIntentId = 123456;
         PendingIntent mPendingIntent = PendingIntent.getActivity(context.getApplicationContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
         AlarmManager mgr = (AlarmManager) context.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
@@ -122,4 +123,22 @@ public class AppUtil {
         android.os.Process.killProcess(android.os.Process.myPid());
         System.exit(0);
     }
+
+    public static void reboot(Context context) {
+        Process process;
+        PrintWriter printWriter;
+        try {
+            process = Runtime.getRuntime().exec("su");
+            printWriter = new PrintWriter(process.getOutputStream());
+            printWriter.println("reboot");
+            printWriter.flush();
+            printWriter.close();
+            process.waitFor();
+        } catch (Exception e) {
+            e.printStackTrace();
+
+            //重启app
+            restartApp(context);
+        }
+    }
 }

+ 191 - 0
app/src/main/java/com/example/informationkanban/utils/HttpHelper.java

@@ -0,0 +1,191 @@
+package com.example.informationkanban.utils;
+
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+
+public class HttpHelper {
+    private static OkHttpClient okHttpClient = null;
+
+    public static final String FILE_APK_PATH = Environment.getExternalStorageDirectory() + "/my_app";
+    public static final String FILE_APK_NAME = "info_board.apk";
+
+    /**
+     * @param url   服务器地址
+     * @param file  所要上传的文件
+     */
+    public static void upload(String url, File file, Object tag, UploadCallback callback) {
+        if (okHttpClient == null) {
+            okHttpClient = new OkHttpClient();
+        }
+
+        RequestBody requestBody = new MultipartBody.Builder()
+                .setType(MultipartBody.FORM)
+                .addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file))
+                .build();
+
+        Request request = new Request.Builder()
+                .url(url)
+                .tag(tag)
+                .post(requestBody)
+                .build();
+
+        okHttpClient.newCall(request).enqueue(new Callback() {
+            @Override
+            public void onFailure(Call call, IOException e) {
+                if (callback != null) {
+                    callback.onFail();
+                }
+            }
+
+            @Override
+            public void onResponse(Call call, Response response) throws IOException {
+                Log.d("upload", "voice msg response: " + response.toString());
+               if( response.code()==200 && response.body() != null) {
+                   String data = response.body().string();
+                   //voice msg response: upload/file/202104102037715.mp3
+                   if (callback != null) {
+                       callback.onSuccess(data);
+                   }
+               } else {
+                   if (callback != null) {
+                       callback.onFail();
+                   }
+               }
+            }
+        });
+    }
+
+    public static void download(String url, Object tag, final DownloadListener listener) {
+        if (okHttpClient == null) {
+            okHttpClient = new OkHttpClient();
+        }
+
+        Request request = new Request.Builder()
+                .url(url)
+                .tag(tag)
+                .build();
+
+        okHttpClient.newCall(request).enqueue(new Callback() {
+            @Override
+            public void onFailure(Call call, IOException e) {
+                Log.d("download", "onFailure==" + e.toString());
+                if (listener != null) {
+                    listener.onDownloadFailed(); // 下载失败
+                }
+            }
+
+            @Override
+            public void onResponse(Call call, Response response) {
+                Log.d("download", "response==" + response.body().contentLength());
+                InputStream is = null;
+                byte[] buf = new byte[2048];
+                int len;
+                FileOutputStream fos = null;
+                try {
+                    is = response.body().byteStream();
+                    long total = response.body().contentLength();
+                    File file = new File(isHaveExistDir(new File(FILE_APK_PATH), new File(FILE_APK_PATH + "/" + FILE_APK_NAME)), FILE_APK_NAME);
+                    fos = new FileOutputStream(file);
+                    long sum = 0;
+                    while ((len = is.read(buf)) != -1) {
+                        fos.write(buf, 0, len);
+                        sum = sum + (long) len;
+                        //int progress = (int) (sum * 1.0f / total * 100);
+                        float sp = (float) sum / (float) total;
+                        int progress = (int) (sp * 100);
+                        Log.d("download", "progress==" + progress);
+                        if (listener != null) {
+                            listener.onDownloading(progress);// 下载中
+                        }
+                    }
+                    fos.flush();
+                    if (listener != null) {
+                        listener.onDownloadSuccess(); // 下载完成
+                    }
+                } catch (Exception e) {
+                    Log.d("download", "Exception==");
+                    if (listener != null) {
+                        listener.onDownloadFailed();
+                    }
+                } finally {
+                    try {
+                        if (is != null)
+                            is.close();
+                        if (fos != null)
+                            fos.close();
+                    } catch (IOException e) {
+                        Log.d("download", "IOException==");
+                    }
+                }
+            }
+        });
+    }
+
+    public static void cancelRequestByTag(Object tag) {
+        if (okHttpClient != null && tag != null) {
+            for (Call call : okHttpClient.dispatcher().queuedCalls()) {
+                if (tag.equals(call.request().tag())) {
+                    call.cancel();
+                }
+            }
+
+            for (Call call : okHttpClient.dispatcher().runningCalls()) {
+                if (tag.equals(call.request().tag())) {
+                    call.cancel();
+                }
+            }
+        }
+    }
+
+    private static String isHaveExistDir(File downloadFile, File sonFile) throws IOException {
+        Log.d("download", "downloadFile.mkdirs()==" + downloadFile.mkdirs());
+        Log.d("download", "sonFile.mkdir()==" + sonFile.mkdir());
+        if (!downloadFile.mkdirs()) {
+            downloadFile.createNewFile();
+        }
+        deleteAPKFile(sonFile);//只要文件名相同就可以自动替换(按道理此处不需要了,但为了保险起见还是先执行删除操作)。
+        return downloadFile.getAbsolutePath();
+    }
+
+    public static boolean deleteAPKFile(File downloadFile) {
+        return downloadFile.delete();
+    }
+
+
+    public interface UploadCallback{
+        void onFail();
+        void onSuccess(String data);
+    }
+
+    public interface DownloadListener {
+        /**
+         * 下载成功
+         */
+        void onDownloadSuccess();
+
+        /**
+         * @param progress 下载进度
+         */
+        void onDownloading(int progress);
+
+        /**
+         * 下载失败
+         */
+        void onDownloadFailed();
+    }
+}

+ 11 - 7
app/src/main/java/com/example/informationkanban/utils/LanguageSetDialogHelper.java

@@ -3,6 +3,7 @@ package com.example.informationkanban.utils;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -83,9 +84,7 @@ public class LanguageSetDialogHelper {
         buttonCancel.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                if (dialog != null) {
-                    dialog.dismiss();
-                }
+                dismissDialog();
             }
         });
 
@@ -97,7 +96,7 @@ public class LanguageSetDialogHelper {
                 //if (SettingConfig.getLanguageMode(activity) == 1) {
                     if (selectIndex != originIndex) {
                         Toast.makeText(activity, "restart now...", Toast.LENGTH_LONG).show();
-                        new Handler().postDelayed(new Runnable() {
+                        new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
                             @Override
                             public void run() {
                                 AppUtil.restartApp(activity);
@@ -114,9 +113,7 @@ public class LanguageSetDialogHelper {
                     }, 3000);
                 }*/
 
-                if (dialog != null) {
-                    dialog.dismiss();
-                }
+                dismissDialog();
             }
         });
 
@@ -137,4 +134,11 @@ public class LanguageSetDialogHelper {
             e.printStackTrace();
         }
     }
+
+    public static void dismissDialog() {
+        if (dialog != null) {
+            dialog.dismiss();
+            dialog = null;
+        }
+    }
 }

+ 224 - 0
app/src/main/java/com/example/informationkanban/utils/XCrashUtils.java

@@ -0,0 +1,224 @@
+package com.example.informationkanban.utils;
+
+import android.app.AlarmManager;
+import android.app.Application;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.util.Log;
+
+import com.example.informationkanban.BuildConfig;
+import com.example.informationkanban.common.Constant;
+
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.FormBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import xcrash.ICrashCallback;
+import xcrash.TombstoneManager;
+import xcrash.TombstoneParser;
+import xcrash.XCrash;
+
+public class XCrashUtils {
+    private final static String TAG = "XCrashUtils";
+
+    private Application app;
+    private OkHttpClient okHttpClient;
+
+    // callback for java crash, native crash and ANR
+    private final ICrashCallback callback = new ICrashCallback() {
+        @Override
+        public void onCrash(String logPath, String emergency) {
+            Log.d(TAG, "log path: " + (logPath != null ? logPath : "(null)") + ", emergency: " + (emergency != null ? emergency : "(null)"));
+
+            if (emergency != null) {
+                debug(logPath, emergency);
+
+                // Disk is exhausted, send crash report immediately.
+                //sendThenDeleteCrashLog(logPath, emergency);
+            } else {
+                // Add some expanded sections. Send crash report at the next time APP startup.
+
+                // OK
+                TombstoneManager.appendSection(logPath, "expanded_key_1", "expanded_content");
+                TombstoneManager.appendSection(logPath, "expanded_key_2", "expanded_content_row_1\nexpanded_content_row_2");
+
+                // Invalid. (Do NOT include multiple consecutive newline characters ("\n\n") in the content string.)
+                // TombstoneManager.appendSection(logPath, "expanded_key_3", "expanded_content_row_1\n\nexpanded_content_row_2");
+
+                debug(logPath, null);
+            }
+
+            // Disk is exhausted, send crash report immediately.
+            //sendThenDeleteCrashLog(logPath, emergency);
+
+            //非debug版本上传crash日志
+            if (!BuildConfig.DEBUG) {
+                uploadCrashLog(logPath);
+            } else {
+                AppUtil.reboot(app);
+            }
+        }
+    };
+
+    //ANR Catcher
+    private final ICrashCallback anrCallback = new ICrashCallback() {
+        @Override
+        public void onCrash(String logPath, String emergency) {
+            Log.d(TAG, "log path: " + (logPath != null ? logPath : "(null)") + ", emergency: " + (emergency != null ? emergency : "(null)"));
+
+            if (emergency != null) {
+                debug(logPath, emergency);
+
+                // Disk is exhausted, send crash report immediately.
+                //sendThenDeleteCrashLog(logPath, emergency);
+            } else {
+                // Add some expanded sections. Send crash report at the next time APP startup.
+
+                // OK
+                TombstoneManager.appendSection(logPath, "expanded_key_1", "expanded_content");
+                TombstoneManager.appendSection(logPath, "expanded_key_2", "expanded_content_row_1\nexpanded_content_row_2");
+
+                // Invalid. (Do NOT include multiple consecutive newline characters ("\n\n") in the content string.)
+                // TombstoneManager.appendSection(logPath, "expanded_key_3", "expanded_content_row_1\n\nexpanded_content_row_2");
+
+                debug(logPath, null);
+            }
+
+            AppUtil.reboot(app);
+        }
+    };
+
+    public void init(Application application) {
+        Log.d(TAG, "xCrash SDK init: start");
+        app = application;
+
+        // Initialize xCrash.
+        XCrash.init(application, new XCrash.InitParameters()
+                .setAppVersion(BuildConfig.VERSION_NAME)
+                .setJavaRethrow(true)
+                .setJavaLogCountMax(10)
+                .setJavaDumpAllThreadsWhiteList(new String[]{"^main$", "^Binder:.*", ".*Finalizer.*"})
+                .setJavaDumpAllThreadsCountMax(10)
+                .setJavaCallback(callback)
+                .setNativeRethrow(true)
+                .setNativeLogCountMax(10)
+                .setNativeDumpAllThreadsWhiteList(new String[]{"^xcrash\\.sample$", "^Signal Catcher$", "^Jit thread pool$", ".*(R|r)ender.*", ".*Chrome.*"})
+                .setNativeDumpAllThreadsCountMax(10)
+                .setNativeCallback(callback)
+                .setAnrRethrow(true)
+                .setAnrLogCountMax(10)
+                .setAnrCallback(anrCallback)
+                .setPlaceholderCountMax(3)
+                .setPlaceholderSizeKb(512)
+                .setLogDir(application.getExternalFilesDir("xcrash").toString())
+                .setLogFileMaintainDelayMs(5000));
+    }
+
+    private void uploadCrashLog(String path) {
+        final File logFile = new File(path);
+        if (logFile.exists()) {
+            String url = NetFunctionConfig.getServerIp() + ":" + Constant.URL_PORT + "/ncs/upload/file";
+            HttpHelper.upload(url, logFile, TAG, new HttpHelper.UploadCallback() {
+                @Override
+                public void onFail() {
+                    Log.e(TAG,"错误日志文件上传失败!");
+                    AppUtil.reboot(app);
+                }
+
+                @Override
+                public void onSuccess(String data) {
+                    Log.e(TAG,"错误日志文件上传成功!" + data);
+                    sendThenDeleteCrashLog(url + data, null);
+                    //删除日志文件
+                    //logFile.delete();
+                }
+            });
+        }
+    }
+
+    private void sendThenDeleteCrashLog(final String logPath, String emergency) {
+        try {
+            // Parse
+            //Map<String, String> map = TombstoneParser.parse(logPath, emergency);
+            //String crashReport = new JSONObject(map).toString();
+
+            if(okHttpClient == null){
+                okHttpClient = new OkHttpClient();
+            }
+
+            FormBody.Builder formBody = new FormBody.Builder();
+            formBody.add("class_name", app.getPackageName());
+            formBody.add("method_name", "");
+            formBody.add("exception_name", "");
+            formBody.add("err_msg", "");
+            formBody.add("stack_trace", logPath);
+
+            String url = NetFunctionConfig.getServerIp() + ":" + Constant.URL_PORT;
+            Request request  = new Request.Builder()
+                    .url(url + "/device/error_log")
+                    .post(formBody.build())
+                    .build();
+
+            okHttpClient.newCall(request).enqueue(new Callback() {
+                @Override
+                public void onFailure(Call call, IOException e) {
+                    Log.e(TAG,"错误日志名称上传失败"+e.getMessage());
+                    AppUtil.reboot(app);
+                }
+
+                @Override
+                public void onResponse(Call call, Response response) throws IOException {
+                    Log.d(TAG,"错误日志名称上传成功");
+                    TombstoneManager.deleteTombstone(logPath);
+                    //String data = response.body().string();
+                    //Log.d(TAG,"错误日志数据 data "+data);
+                    AppUtil.reboot(app);
+                }
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
+            AppUtil.reboot(app);
+        }
+
+        // Send the crash report to server-side.
+        // ......
+
+        // If the server-side receives successfully, delete the log file.
+        //
+        // Note: When you use the placeholder file feature,
+        //       please always use this method to delete tombstone files.
+        //
+        //TombstoneManager.deleteTombstone(logPath);
+    }
+
+    private void debug(String logPath, String emergency) {
+        // Parse and save the crash info to a JSON file for debugging.
+        FileWriter writer = null;
+        try {
+            File debug = new File(XCrash.getLogDir() + "/debug.json");
+            debug.createNewFile();
+            writer = new FileWriter(debug, false);
+            writer.write(new JSONObject(TombstoneParser.parse(logPath, emergency)).toString());
+        } catch (Exception e) {
+            Log.d(TAG, "debug failed", e);
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (Exception ignored) {
+                }
+            }
+        }
+    }
+
+}

+ 5 - 0
app/src/main/res/drawable/ic_app_launch.xml

@@ -0,0 +1,5 @@
+<vector android:height="48dp" android:tint="#398EF6"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FFFFFF" android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM15,18L4,18v-4h11v4zM15,13L4,13L4,9h11v4zM20,18h-4L16,9h4v9z"/>
+</vector>

+ 5 - 0
app/src/main/res/drawable/selector_login_botton.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/selector_login_button_item_true" android:state_focused="true"/>
+    <item android:drawable="@drawable/selector_login_button_item_false"/>
+</selector>

+ 5 - 0
app/src/main/res/drawable/selector_login_button_item_false.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+    <solid android:color="#8c8c8c" />
+    <corners android:radius="4dp"/>
+</shape>

+ 5 - 0
app/src/main/res/drawable/selector_login_button_item_true.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+    <solid android:color="#F57401" />
+    <corners android:radius="4dp"/>
+</shape>

+ 7 - 1
app/src/main/res/layout/init_layout.xml

@@ -40,17 +40,21 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="20dp">
+
         <Button
             android:id="@+id/btn_change_language"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:background="@drawable/selector_login_botton"
             android:text="@string/setting_language"
-            android:textSize="20sp"/>
+            android:textSize="20sp" />
+
         <Button
             android:id="@+id/btn_change_ip"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginLeft="20dp"
+            android:background="@drawable/selector_login_botton"
             android:text="@string/setting_change_server"
             android:textSize="20sp"/>
         <Button
@@ -58,6 +62,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginLeft="20dp"
+            android:background="@drawable/selector_login_botton"
             android:text="@string/enter_settings"
             android:textSize="20sp"/>
         <TextView
@@ -97,6 +102,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="10dp"
+        android:background="@drawable/selector_login_botton"
         android:text="@string/enter_info_board"
         android:textSize="20sp"
         android:visibility="invisible"/>

+ 2 - 0
app/src/main/res/layout/language_set_dialog.xml

@@ -62,6 +62,7 @@
             android:id="@+id/confirm_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:background="@drawable/selector_login_botton"
             android:padding="8dp"
             android:text="@string/str_confirm"
             android:textSize="20sp" />
@@ -71,6 +72,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginLeft="80dp"
+            android:background="@drawable/selector_login_botton"
             android:padding="8dp"
             android:text="@string/str_cancel"
             android:textSize="20sp" />