「drone」开发记录

2021-02-01 15:19:52 浏览数 (1)

斜体文字被切掉了一块

TextView.setText的时候在右边加上一个空格。

或者string.xml中添加 来占位。

代码语言:javascript复制
<string name="dp_page_ready">READY&#x200A;</string>

或者在TextView.setText的时候加上一个空格。

动态给LinearLayout添加子View

一列27个自定义view,如果要写到xml里就太麻烦了。 在Java代码中新建子View,设置LayoutParams,然后添加到LinearLayout里。

代码语言:javascript复制
private void initGrids() {
    final int bigGridHeightPx = (int) dpToPx(10);
    final int bigGrid2MarginVerticalPx = (int) dpToPx(3);
    final int smallGridHeightPx = (int) dpToPx(6); // 这里有27个格点
    final int smallGridMarginBotPx = (int) dpToPx(1);

    LinearLayout linearLayoutLeft1 = findViewById(R.id.dp_page_g_field_left1);
    LinearLayout linearLayoutLeft2 = findViewById(R.id.dp_page_g_field_left2);
    mLeftGridList = new ArrayList<>();
    GridsHorView g1 = new GridsHorView(this);
    GridsHorView g2 = new GridsHorView(this);
    GridsHorView g3 = new GridsHorView(this);

    LinearLayout.LayoutParams p1 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, bigGridHeightPx);
    LinearLayout.LayoutParams p2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, bigGridHeightPx);
    p2.setMargins(0, bigGrid2MarginVerticalPx, 0, bigGrid2MarginVerticalPx);
    linearLayoutLeft1.addView(g1, p1);
    linearLayoutLeft1.addView(g2, p2);
    linearLayoutLeft1.addView(g3, p1);
    mLeftGridList.addAll(Arrays.asList(g1, g2, g3));

    for (int i = 0; i < 27; i  ) {
        GridsHorView smallGrid = new GridsHorView(this);
        smallGrid.setAlphaValue((int) (255 * (1 - 0.02 * i)));
        LinearLayout.LayoutParams sp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, smallGridHeightPx);
        sp.setMargins(0, 0, 0, smallGridMarginBotPx);
        mLeftGridList.add(smallGrid);
        linearLayoutLeft2.addView(smallGrid, sp);
    }

    for (GridsHorView g : mLeftGridList) {
        g.setOri(GridsHorView.Ori.RIGHT_TO_LEFT);
        g.disableMode();
        g.setCubeCount(1);
    }

    // 初始化右边(P2)的格子...
}

private float dpToPx(float dp) {
    return dp * getResources().getDisplayMetrics().density;
}

获取当前WiFi的名字

需要定位权限。

代码语言:javascript复制
public static String getWiFiName(Context context) {
    String wifiId = "WIFI_NAME_NOT_FOUND";
    try {
        WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        WifiInfo info = wifiMgr.getConnectionInfo();
        wifiId = info != null ? info.getSSID() : null;
    } catch (Exception e) {
        e.printStackTrace();
    }
    if (!TextUtils.isEmpty(wifiId) && wifiId.startsWith(""")) {
        wifiId = wifiId.substring(1); // 删去前面那个引号
    }
    return wifiId;
}

这个方法适用于判断WiFi名称的前缀。

检查权限的方法

代码语言:javascript复制
protected void checkPermission(String[] permissions, final int reqCode) {
    List<String> perList = new ArrayList<>();
    for (String p : permissions) {
        if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, p)) {
            LL.e("没有权限: "   p);
            perList.add(p);
        }
    }
    if (!perList.isEmpty()) {
        String[] per = new String[perList.size()];
        for (int i = 0; i < per.length; i  ) {
            per[i] = perList.get(i);
        }
        ActivityCompat.requestPermissions(this, per, reqCode);
    }
}

设置Click监听器

应用在Activity中,给一堆view设置同一个监听器

代码语言:javascript复制
// 设置点击监听器
protected void setOnClickListeners(View.OnClickListener l, View... views) {
    for (View v : views) {
        v.setOnClickListener(l);
    }
}

protected void setOnClickListeners(View.OnClickListener l, int... resIds) {
    for (int r : resIds) {
        findViewById(r).setOnClickListener(l);
    }
}

设置字体

先把字体加载好。

代码语言:javascript复制
protected void setTvPangMenAndItalic(int... tvResIds) {
    for (int i : tvResIds) {
        ((TextView) findViewById(i)).setTypeface(AppControl.getPangMenTf(), Typeface.ITALIC);
    }
}

protected void setTvPangMenAndItalic(TextView... tvs) {
    for (TextView t : tvs) {
        t.setTypeface(AppControl.getPangMenTf(), Typeface.ITALIC);
    }
}

收起软键盘

代码语言:javascript复制
void hideSoftKeyboard() {
    try {
        InputMethodManager inputMgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        inputMgr.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    } catch (Exception e) {
        LL.e("rustApp", e);
    }
}

判断网络是否有连接

代码语言:javascript复制
protected boolean isNetworkAvailable(Context context) {
    ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

    if (connectivity == null) {
        return false;
    } else {
        NetworkInfo[] info = connectivity.getAllNetworkInfo();
        if (info != null) {
            for (NetworkInfo anInfo : info) {
                if (anInfo.getState() == NetworkInfo.State.CONNECTED) {
                    return true;
                }
            }
        }
    }
    return false;
}

判断某个App是否安装

获取PackageManager通过包名来判断某个App是否安装。 但是有的手机在获取PackageManager的时候就能抛出异常。

代码语言:javascript复制
protected boolean appInstalledOrNot(String uri) {
    try {
        PackageManager pm = getPackageManager();
        pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
        return true;
    } catch (Exception e) {
        LL.e("appInstalledOrNot: "   uri, e);
    }
    return false;
}

总而言之这并不是个很好的办法。

性能优化

一些性能优化的处理措施和思考。

给UI线程更多的CPU资源

数据库的操作放在子线程中进行。数据库线程也可能会和UI线程争抢CPU的时间片。 假设数据库要删除大量数据(比如1万条)。 那么我们可以尝试在数据库处理了某个数量(例如1千)的操作后,sleep一下,给UI线程让出CPU时间。 但现在一般都是多核手机,具体效果有待考量。

log记录工具

把log写到文件里。用RandomAccessFile与MappedByteBuffer写日志到文件中。任务处理放在子线程中,由HandlerThread来管理。

代码语言:javascript复制
public class LL {

    public static abstract class Level {
        public static final String D = "D"; // 普通debug
        public static final String W = "W"; // 警告
        public static final String E = "E"; // 错误
    }

    private static String defTag = "App";

    private static boolean showLogcat = true;
    private static boolean writeFile = true;

    // 注意申请SD卡读写权限
    private static String logFileDir = Environment.getExternalStorageDirectory().getAbsolutePath()  
            File.separator   "rust"   File.separator   "logs";

    private static String fileName;

    private static List<LogListener> listenerList = new ArrayList<>();

    private static HandlerThread handlerThread;
    private static Handler writerHandler;

    private static final int LOG_FILE_GROW_SIZE = 1024 * 10; // log文件每次增长的大小
    private static long gCurrentLogPos = 0;                  // log文件当前写到的位置 - 注意要单线程处理

    /**
     * 使用前必须调用此方法进行准备
     *
     * @param context 建议传入applicationContext
     * @param fileDir 存放log文件的目录
     */
    public static void prepare(Context context, @NonNull String fileDir, String logFilePrefix) {
        gCurrentLogPos = 0;
        if (TextUtils.isEmpty(fileDir)) {
            if (Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)) {
                logFileDir = Environment.getExternalStorageDirectory().getAbsolutePath()  
                        File.separator   "rust"   File.separator   "logs";
            } else {
                logFileDir = context.getFilesDir().getAbsolutePath()  
                        File.separator   "rust"   File.separator   "logs";
            }
        } else {
            logFileDir = fileDir;
        }
        if (null == handlerThread) {
            handlerThread = new HandlerThread("LL");
            handlerThread.start();
        }
        writerHandler = new Handler(handlerThread.getLooper());
        fileName = logFilePrefix   "_"   System.currentTimeMillis()   ".txt";
        Log.d(defTag, "[prepare] file: "   fileName);
    }

    public static String getLogFileDir() {
        return logFileDir;
    }

    public static String getFileName() {
        return fileName;
    }

    // 退出
    public static void quit() {
        if (writerHandler != null) {
            writerHandler.removeCallbacksAndMessages(null);
        }
        if (handlerThread != null) {
            handlerThread.quit();
        }
    }

    public static void setDefTag(String t) {
        LL.defTag = t;
    }

    public static void setWriteFile(boolean w) {
        LL.writeFile = w;
    }

    public static void d(String content) {
        d(defTag, content, writeFile);
    }

    public static void d(String tag, String content) {
        d(tag, content, writeFile);
    }

    public static void dn(String content) {
        d(defTag, content, false);
    }

    // 不写到文件中
    public static void dn(String tag, String content) {
        d(tag, content, false);
    }

    public static void d(String tag, String content, boolean write) {
        if (showLogcat) {
            Log.d(tag, content);
        }
        tellLog(Level.D, tag, content);
        if (write) {
            if (writerHandler != null) {
                writerHandler.post(new WriteRunnable(tag, content));
            }
        }
    }

    // log级别 WARN - w
    public static void w(String content) {
        w(defTag, content, writeFile);
    }

    public static void w(String tag, String content) {
        w(tag, content, writeFile);
    }

    // 不写到文件中
    public static void wn(String content) {
        w(defTag, content, false);
    }

    // 不写到文件中
    public static void wn(String tag, String content) {
        w(tag, content, false);
    }

    public static void w(String tag, String content, boolean write) {
        if (showLogcat) {
            Log.w(tag, content);
        }
        tellLog(Level.W, tag, content);
        if (write) {
            if (writerHandler != null) {
                writerHandler.post(new WriteRunnable(tag, content));
            }
        }
    }

    public static void e(String content) {
        e(defTag, content);
    }

    public static void e(String tag, String content) {
        e(tag, content, writeFile);
    }

    public static void e(String tag, Exception e) {
        e(tag, e.getMessage(), writeFile);
    }

    // 只打log  不写文件
    public static void en(String tag, String content) {
        e(tag, content, false);
    }

    public static void e(String tag, String content, boolean write) {
        if (showLogcat) {
            Log.e(tag, content);
        }
        tellLog(Level.E, tag, content);
        if (write) {
            if (writerHandler != null) {
                writerHandler.post(new WriteRunnable(tag, content));
            }
        }
    }

    private static void tellLog(String level, String tag, String content) {
        if (null != listenerList) {
            for (LogListener l : listenerList) {
                l.onLog(level, tag, content);
            }
        }
    }

    public static void addListener(LogListener l) {
        if (null == listenerList) {
            listenerList = new ArrayList<>();
        }
        listenerList.add(l);
    }

    public static void removeListener(LogListener l) {
        if (null != listenerList) {
            listenerList.remove(l);
        }
    }

    static class WriteRunnable implements Runnable {
        String mmTag;
        String mmContent;

        WriteRunnable(String tag, String content) {
            this.mmTag = tag;
            this.mmContent = content;
        }

        @Override
        public void run() {
            SimpleDateFormat logTimeFormat = new SimpleDateFormat("HH:mm:ss.SSS", Locale.CHINA);
            String logContent = logTimeFormat.format(new Date())   " ["   mmTag   "] "   mmContent   "rn";
            try {
                File dir = new File(logFileDir);
                if (!dir.exists()) {
                    boolean mk = dir.mkdirs();
                    Log.d(defTag, "make dir "   mk);
                }
                File eFile = new File(logFileDir   File.separator   fileName);
                byte[] strBytes = logContent.getBytes();
                try {
                    RandomAccessFile randomAccessFile = new RandomAccessFile(eFile, "rw");
                    MappedByteBuffer mappedByteBuffer;
                    final int inputLen = strBytes.length;
                    if (!eFile.exists()) {
                        boolean nf = eFile.createNewFile();
                        Log.d(defTag, "new log file "   nf);
                        mappedByteBuffer = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, gCurrentLogPos, LOG_FILE_GROW_SIZE);
                    } else {
                        mappedByteBuffer = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, gCurrentLogPos, inputLen);
                    }
                    if (mappedByteBuffer.remaining() < inputLen) {
                        mappedByteBuffer = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, gCurrentLogPos, LOG_FILE_GROW_SIZE   inputLen);
//                        Log.d(defTag, "run: grow size ");
                    }
//                    Log.d(defTag, "run: gCurrentLogPos: "   gCurrentLogPos   ", pos: "   mappedByteBuffer.position()   ", remaining: "   mappedByteBuffer.remaining());
                    mappedByteBuffer.put(strBytes);
                    gCurrentLogPos  = inputLen;
                } catch (Exception e) {
                    Log.e(defTag, "WriteRunnable run: ", e);
                    if (!eFile.exists()) {
                        boolean nf = eFile.createNewFile();
                        Log.d(defTag, "new log file "   nf);
                    }
                    FileOutputStream os = new FileOutputStream(eFile, true);
                    os.write(logContent.getBytes());
                    os.flush();
                    os.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(defTag, "写log文件出错: ", e);
            }
        }
    }

}

监听器

代码语言:javascript复制
public abstract class LogListener {
    public abstract void onLog(String level, String tag, String content);
}

自定义View

一些自定义的view,比如一些折线图,条形图。

折线图 ColorAutoLineChart

单独一条折线,可自动缩放Y轴高度。使用FloatBuffer来存储数据。

代码语言:javascript复制
public class ColorAutoLineChart extends View {

    private static final String TAG = "AppColorAutoLineChart";

    private float yMax = 1856f;
    private float yMin = -1024f;

    // 图表线条在view顶部留出的间距
    float viewYStart = 2;
    float axisTextSize = 7;

    private int onShowPointsCount = 256;  // 当前显示的数据个数
    private int cacheMaxPoint = 9000;     // 数据存储最大个数

    float axisLineWid = 1f; // 坐标轴线条宽度
    int dataLineWid = 2;

    // 数据线颜色
    private int dataColor = Color.WHITE;

    private float xStep = 1.0f;
    private float viewWidth;
    private float viewHeight;
    private float botLeftXOnView = 0; // 图表左下点在view中的x坐标
    private float botLeftYOnView = 0;
    private float originYToBottom = 20; // 图表原点距离view底部的距离

    private FloatBuffer dataBuffer;

    private Paint bgPaint;
    private Paint linePaint;
    private Paint wavePaint;
    Path wavePath = new Path(); // 用来画渐变色
    int waveTopColor = Color.parseColor("#f65212");

    public ColorAutoLineChart(Context context) {
        this(context, null);
    }

    public ColorAutoLineChart(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ColorAutoLineChart(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public void addData(int[] data) {
        for (int i : data) {
            addData(i);
        }
    }

    public void addData(float data) {
        dataBuffer.put(data);
        if (dataBuffer.position() > (dataBuffer.capacity() * 2 / 3)) {
            float[] bufferArr = dataBuffer.array();
            System.arraycopy(bufferArr, dataBuffer.position() - cacheMaxPoint, bufferArr, 0, cacheMaxPoint);
            dataBuffer.position(cacheMaxPoint);
//            Log.d(TAG, "把当前数据移动到buffer起始位置 "   dataBuffer);
        }
        invalidate();
    }

    private void init(Context context) {
        dataBuffer = FloatBuffer.allocate(3 * cacheMaxPoint); // 分配3倍的空间
        bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        wavePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        bgPaint.setStrokeWidth(axisLineWid);
        bgPaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth(dataLineWid);
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setColor(dataColor);

        botLeftXOnView = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0, context.getResources().getDisplayMetrics());
        originYToBottom = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());
        viewYStart = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics());
        axisLineWid = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics());
        axisTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 8, context.getResources().getDisplayMetrics());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        viewWidth = getWidth();
        viewHeight = getHeight();
        botLeftYOnView = viewHeight - originYToBottom;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.TRANSPARENT);
        xStep = (viewWidth - botLeftXOnView) / (onShowPointsCount - 1);

        int dataStartIndexInBuffer = 0; // 数据在buffer中的起始下标
        if (dataBuffer.position() > onShowPointsCount) {
            dataStartIndexInBuffer = dataBuffer.position() - onShowPointsCount;
        }
        float[] bufferArr = dataBuffer.array();
        float maxData = bufferArr[0];
        float minData = bufferArr[0];
        for (int i = dataStartIndexInBuffer; i < dataBuffer.position(); i  ) {
            float cur = bufferArr[i];
            if (cur < minData) {
                minData = cur;
            } else if (cur > maxData) {
                maxData = cur;
            }
        }

        drawWave(canvas, dataStartIndexInBuffer);
    }

    private void drawWave(Canvas canvas, int dataStartIndexInBuffer) {
        wavePath.reset();
        final float yDataRange = yMax - yMin;
        final float yAxisRangeOnView = botLeftYOnView - viewYStart;
        final float yDataStep = yAxisRangeOnView / yDataRange;

        float[] dataArr = dataBuffer.array();
        float maxData = dataArr[dataStartIndexInBuffer];

        float waveStartX = botLeftXOnView;
        float waveStartY = getYL(dataArr[dataStartIndexInBuffer], yDataStep);
        wavePath.moveTo(waveStartX, waveStartY);

        for (int i = dataStartIndexInBuffer; i < dataBuffer.position() - 1; i  ) {
            float curData = dataArr[i];
            float nextData = dataArr[i   1];
            wavePath.lineTo(botLeftXOnView   (i - dataStartIndexInBuffer   1) * xStep, getYL(nextData, yDataStep));
            canvas.drawLine(botLeftXOnView   (i - dataStartIndexInBuffer) * xStep, getYL(curData, yDataStep),
                    botLeftXOnView   (i - dataStartIndexInBuffer   1) * xStep, getYL(nextData, yDataStep),
                    linePaint);
            maxData = Math.max(maxData, nextData);
        }
        wavePath.lineTo(viewWidth, viewHeight);
        wavePath.lineTo(botLeftXOnView, viewHeight);
        wavePath.lineTo(waveStartX, waveStartY);
        wavePath.close();
        wavePaint.setShader(new LinearGradient(0, getYL(maxData, yDataStep), 0, viewHeight, waveTopColor, Color.TRANSPARENT, Shader.TileMode.CLAMP));
        canvas.drawPath(wavePath, wavePaint);
    }

    private float getYL(final float yData, float yDataStep) {
        return botLeftYOnView - (yData - yMin) * yDataStep;
    }

}

后台用户行为记录

最开始设计后台服务的时候,并没有考虑到太多的记录功能。仅仅记录了用户登录行为。 今后应该记录更详细的。例如获取用户信息,时间,客户端类型,userID等等。获取用户信息是否成功,可作为缓存登录的依据。

后台可以记录的用户行为,例如获取用户信息,用户查看飞行记录列表,用户查看飞行记录详情,用户点赞。

WebView设置

webview不显示图片的问题。LL后加载https的网页,默认会不加载http的资源。需要设置。

代码语言:javascript复制
webSettings.setBlockNetworkImage(false);
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);

方法调用记录调用原因

调用某个方法的时候,比如中止某项功能。可以在log上记录一些原因,方便debug。

Glide

设定播放gif的次数

代码语言:javascript复制
Glide.get(getApplicationContext()).setMemoryCategory(MemoryCategory.NORMAL);
Glide.with(this).asGif().listener(new RequestListener<GifDrawable>() {
    @Override
    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<GifDrawable> target, boolean isFirstResource) {
        return false;
    }

    @Override
    public boolean onResourceReady(GifDrawable resource, Object model, Target<GifDrawable> target, DataSource dataSource, boolean isFirstResource) {
        resource.setLoopCount(1);
        return false;
    }
}).load(R.drawable.app_start_up).into(imageView);
imageView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
    @Override
    public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
        MyAppControl.setPhoneStatusBarHeight(insets.getSystemWindowInsetTop());
        return insets;
    }
});

0 人点赞