# Android 开发笔记

Android工程概览 (opens new window)

Intent 和 Intent 过滤器 (opens new window)

Android Studio使用阿里云Aliyun Maven仓库 https://www.cnblogs.com/360minitao/p/11014396.html

阿里云Maven中央仓库为 阿里云云效 (opens new window) 提供的公共代理仓库,帮助研发人员提高研发生产效率,使用阿里云Maven中央仓库作为下载源,速度更快更稳定。

https://maven.aliyun.com/mvn/guide

Fastlane 入门教程:https://www.raywenderlich.com/10187451-fastlane-tutorial-for-android-getting-started

# Android开发流程

# 创建工程

# 写代码

# 构建apk

从命令行构建您的应用 (opens new window)

#构建调试版 APK。这将在 project_name/module_name/build/outputs/apk/ 中创建一个名为 module_name-debug.apk 的 APK。该文件已使用调试密钥进行签名并使用 zipalign 对齐,因此您可以立即将其安装到设备上。
./gradlew assembleDebug
# 发布版apk
./gradlew assembleRelease

# Android 基础

# 入口

# 界面UI

# 多线程

定义一个线程只需要新建一个类继承自Thread ,然后重写父类的run() 方法,并在里面编写耗时逻辑即可

class MyThread extends Thread {

    @Override
    public void run() {
        // 处理具体的逻辑
    }

}

那么该如何启动这个线程呢?其实也很简单,只需要new出MyThread的实例,然后调用它的start() 方法,这样run() 方法中的代码就会在子线程当中运行了,如下所示:

new MyThread().start();

当然,使用继承的方式耦合性有点高,更多的时候我们都会选择使用实现Runnable 接口的方式来定义一个线程,如下所示:

class MyThread implements Runnable {

    @Override
    public void run() {
        // 处理具体的逻辑
    }

}

如果使用了这种写法,启动线程的方法也需要进行相应的改变,如下所示:

MyThread myThread = new MyThread();
new Thread(myThread).start();

如果你不想专门再定义一个类去实现Runnable 接口,也可以使用匿名类的方式,这种写法更为常见,如下所示:

new Thread(new Runnable() {

    @Override
    public void run() {
        // 处理具体的逻辑
    }

}).start();

以上

# 在子线程中更新UI

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    public static final int UPDATE_TEXT = 1;

    private TextView text;

    private Handler handler = new Handler() {
//由于Handler是在主线程中创建的,所以此时`handleMessage()` 方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TEXT:
                    // 在这里可以进行UI操作
                    text.setText("Nice to meet you");
                    break;
                default:
                    break;
            }
        }

    };

    ...

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message); // 将Message对象发送出去
                    }
                }).start();
                break;
            default:
                break;
        }
    }

}

回主线程更新UI

((Activity) context).runOnUiThread(new Runnable() {
    @Override
    public void run() {
        //你的代码 your code here
    }
});

# 异步消息处理机制

Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。其中Message和Handler在上一小节中我们已经接触过了,而MessageQueue和Looper对于你来说还是全新的概念,下面我就对这4个部分进行一下简要的介绍。

  1. Message

    Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上一小节中我们使用到了Message的what 字段,除此之外还可以使用arg1arg2 字段来携带一些整型数据,使用obj 字段携带一个Object 对象。

  2. Handler

    Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage() 方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage() 方法中。

  3. MessageQueue

    MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue 对象。

  4. Looper

    Looper是每个线程中的MessageQueue的管家,调用Looper的loop() 方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage() 方法中。每个线程中也只会有一个Looper 对象。

首先需要在主线程当中创建一个Handler 对象,并重写handleMessage() 方法。

然后当子线程中需要进行UI操作时,就创建一个Message 对象,并通过Handler将这条消息发送出去。

之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的handleMessage() 方法中。由于Handler是在主线程中创建的,所以此时handleMessage() 方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了。整个异步消息处理机制的流程示意图如图10.4所示。

# 使用AsyncTask

一个最简单的自定义AsyncTask就可以写成如下方式:

//这里我们把AsyncTask的第一个泛型参数指定为Void ,表示在执行AsyncTask的时候不需要传入参数给后台任务。
//第二个泛型参数指定为Integer ,表示使用整型数据来作为进度显示单位。
//第三个泛型参数指定为Boolean ,则表示使用布尔型数据来反馈执行结果。
//给任务的参数、进度、执行结果
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    ...
}

重写AsyncTask中的几个方法才能完成对任务的定制。经常需要去重写的方法有以下4个

  1. onPreExecute()

    在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。

  2. doInBackground(Params...)

    这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return 语句来将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Void ,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress (Progress...) 方法来完成。

  3. onProgressUpdate(Progress...)

    当在后台任务中调用了publishProgress(Progress...) 方法后,onProgressUpdate (Progress...)方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。

  4. onPostExecute(Result)

当后台任务执行完毕并通过return 语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。

因此,一个比较完整的自定义AsyncTask就可以写成如下方式:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {

    @Override
    protected void onPreExecute() {
        progressDialog.show(); // 显示进度对话框
    }

    @Override
    protected Boolean doInBackground(Void... params) {
        try {
            while (true) {
                int downloadPercent = doDownload(); // 这是一个虚构的方法
                publishProgress(downloadPercent);
                if (downloadPercent >= 100) {
                    break;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
        // 在这里更新下载进度UI
        progressDialog.setMessage("Downloaded " + values[0] + "%");
    }

    @Override
    protected void onPostExecute(Boolean result) {
        progressDialog.dismiss(); // 关闭进度对话框
        // 在这里提示下载结果
        if (result) {
            Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, " Download failed", Toast.LENGTH_SHORT).show();
        }
    }
}

简单来说,使用AsyncTask的诀窍就是,在doInBackground() 方法中执行具体的耗时任务,在onProgressUpdate() 方法中进行UI操作,在onPostExecute() 方法中执行一些任务的收尾工作。

如果想要启动这个任务,只需编写以下代码即可:

new DownloadTask().execute();

# Code 片段

 Handler(Looper.getMainLooper()).postDelayed({
      //Do something after 100ms
    }, 100)

java

final Handler handler = new Handler(Looper.getMainLooper());
    handler.postDelayed(new Runnable() {
      @Override
      public void run() {
        //Do something after 100ms
      }
    }, 100);

# 服务

简单地说,服务是一种即使用户未与应用交互也可在后台运行的组件,因此,只有在需要服务时才应创建服务。

https://developer.android.com/guide/components/services

服务并不会自动开启线程,所有的代码都是默认运行在主线程当中的。也就是说,我们需要在服务的内部手动创建子线程,并在这里执行具体的任务,否则就有可能出现主线程被阻塞住的情况

您的服务可同时以这两种方式运行,换言之,它既可以是启动服务(以无限期运行),亦支持绑定。唯一的问题在于您是否实现一组回调方法:onStartCommand()(让组件启动服务)和 onBind()(实现服务绑定)。

onCreate() 方法会在服务创建的时候调用

onStartCommand() 方法会在每次服务启动的时候调用

onDestroy() 方法会在服务销毁的时候调用

# 新建一个服务

右键com.example.xxxxx→New→Service→Service

Exported 属性表示是否允许除了当前程序之外的其他程序访问这个服务,Enabled 属性表示是否启用这个服务。将两个属性都勾中,点击Finish完成创建

太长不看 TL;DR

AndroidStudio为我们做的:

app/src/main/AndroidManifest.xml增加了:

<service
android:name=".PandaService"
android:enabled="true"
android:exported="true"></service>

com/example/appjava/PandaService.java文件:

package com.example.appjava;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class PandaService extends Service {
    public PandaService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

# 使用IntentService

# Intent

Intent 是在相互独立的组件(如两个 Activity)之间提供运行时绑定功能的对象。Intent 表示应用执行某项操作的意图。

Intent是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动、启动服务以及发送广播等场景

# Intent 类型

Intent 分为两种类型:

  • 显式 Intent:(Explicit intents)通过提供目标应用的软件包名称或完全限定的组件类名来指定可处理 Intent 的应用。通常,您会在自己的应用中使用显式 Intent 来启动组件,这是因为您知道要启动的 Activity 或服务的类名。例如,您可能会启动您应用内的新 Activity 以响应用户操作,或者启动服务以在后台下载文件。
  • 隐式 Intent :(Implicit intents)不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理。例如,如需在地图上向用户显示位置,则可以使用隐式 Intent,请求另一具有此功能的应用在地图上显示指定的位置。

# 布局

# 线性布局

# orientation="vertical"

从上到下的三个按钮布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 2" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 3" />

</LinearLayout>

改成orientation="horizontal"就是从左到右的三个按钮

注意:如果LinearLayout的排列方向是horizontal,内部的控件就绝对不能将宽度指定为match_parent ,因为这样的话,单独一个控件就会将整个水平方向占满,其他的控件就没有可放置的位置了。同样的道理,如果LinearLayout的排列方向是vertical,内部的控件就不能将高度指定为match_parent

# android:layout_gravity

用于指定控件在布局中的对齐方式。android:layout_gravity 的可选值和android:gravity 差不多,但是需要注意,当LinearLayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上的对齐方式。同样的道理,当LinearLayout的排列方向是vertical时,只有水平方向上的对齐方式才会生效。

# layout_weight

左边一个文本框,右边一个按钮的布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/input_message"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:hint="Type something"
        />

    <Button
        android:id="@+id/send"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Send"
        />

</LinearLayout>

EditText和Button将在水平方向平分宽度

如果Button改成这样:

<Button
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send"
/>

那么右边的按钮宽度是文字宽度,左边的文本框随着手机屏幕动态变化

# 相对布局

四个角和中心的5个Button布局(相对父视图):

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="Button 1" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="Button 2" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Button 3" />

    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:text="Button 4" />

    <Button
        android:id="@+id/button5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:text="Button 5" />

</RelativeLayout>

中间一坨5个Button的布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Button 3" />

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/button3"
        android:layout_toLeftOf="@id/button3"
        android:text="Button 1" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/button3"
        android:layout_toRightOf="@id/button3"
        android:text="Button 2" />

    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/button3"
        android:layout_toLeftOf="@id/button3"
        android:text="Button 4" />

    <Button
        android:id="@+id/button5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/button3"
        android:layout_toRightOf="@id/button3"
        android:text="Button 5" />

</RelativeLayout>

RelativeLayout中还有另外一组相对于控件进行定位的属性,android:layout_alignLeft 表示让一个控件的左边缘和另一个控件的左边缘对齐,android:layout_alignRight 表示让一个控件的右边缘和另一个控件的右边缘对齐。此外,还有android:layout_alignTopandroid:layout_alignBottom ,道理都是一样的,我就不再多说,这几个属性就留给你自己去尝试吧。

# 百分比布局

由于LinearLayout本身已经支持按比例指定控件的大小了,因此百分比布局只为FrameLayout和RelativeLayout进行了功能扩展,提供了PercentFrameLayout和PercentRelativeLayout这两个全新的布局

# 工具篇

# gradle

maven {
    url 'https://maven.aliyun.com/repository/public/'
}

# 小知识

# 远程Web调试

https://developers.google.com/web/tools/chrome-devtools/remote-debugging

chrome://inspect/#devices

edge://inspect/#devices

# com.android.support:appcompat

什么是 AppCompat?

AppCompat 代表 AppCompatibility(应用兼容性)

当 android 的新版本发布时,Google 将不得不支持旧版本的 android。因此,AppCompat 是一组支持库,可用于使用较新版本开发的应用程序与较旧版本协同工作。

例如: 当你创建一个最低 Api 级别为9,目标 Api 级别为21的 android 项目时,Api 级别为10的手机也应该支持像 ActionBar,Drawer Menu (在 Api 9中不存在的新功能)等较新的功能。然后,您可以使用 appCompat 库。因此 android 动作栏将变成支持 android 动作栏/supportFragment 等。

https://stackoverflow.com/a/35553960

修改App包名错误

Error while executing: am start -n "com.topcheer.mlearningz/cn.com.spdb.mlearning.activity.SplashActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -D

# 修改App包名

如果想修改发布的程序包名,则只需要在 app/build.gradle 文件中修改 applicationId 就可以:

android {
    //省略...
    defaultConfig {
        applicationId "cn.com.spdb.mlearning"
    }
    //省略...
}

修改App名称:

src/main/res/values-zh/strings.xml文件里:

<resources>
    <string name="app_name">My Application</string>
</resources>

# VersionCode & VersionName

前因后果: https://dev.mi.com/docs/appsmarket/operation_docs/versionCode&versionName/

  • VersionCode:对消费者不可见,仅用于应用市场、程序内部识别版本,判断新旧等用途。
  • VersionName:展示给消费者,消费者会通过它认知自己安装的版本,下文提到的版本号都是说VersionName

# 退出App

//退出程序
try {
    Thread.sleep(1500);
} catch (InterruptedException e) {
}
//退出程序
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);

# Android抓包

https://www.v2ex.com/t/605666

# 速查手册

  • 当前线程是不是主线程
if(Looper.myLooper() == Looper.getMainLooper()) {
    //Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}

# API级别、API Level

# Android Studio Tips

Pixel 3 API 28 Android9

# 在已经安装AndroidStudio的情况下使用adb:

把下面两行加到~/.zshrc或者~/.bash_profile里:

export ANDROID_HOME=/Users/$USER/Library/Android/sdk
export PATH=${PATH}:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools

# Android Studio调试时,App操作卡顿

mute或者删除所有断点

# Android Studio去掉参数提示

在偏好设置搜里索parameter hints或者Editor->Inlay Hints->Java->parameter hints关闭

# Android Studio键盘快捷键

https://developer.android.com/studio/intro/keyboard-shortcuts

说明 Windows/Linux Mac
常规
全部保存 Ctrl+S Command+S
同步 Ctrl+Alt+Y Command+Option+Y
最大化/最小化编辑器 Ctrl+Shift+F12 Ctrl+Command+F12
添加到收藏夹 Alt+Shift+F Option+Shift+F
使用当前配置文件检查当前文件 Alt+Shift+I Option+Shift+I
快速切换方案 Ctrl+(反引号) | Ctrl+(反引号)
打开设置对话框 Ctrl+Alt+S Command+,(英文逗号)
打开项目结构对话框 Ctrl+Alt+Shift+S Command+;(英文分号)
在标签页和工具窗口之间切换 Ctrl+Tab Ctrl+Tab

# gradle

2017 年 google 后,Android studio 版本更新至 3.0,更新中,连带着 com.android.tools.build:gradle 工具也升级到了 3.0.0,在 3.0.0 中使用了最新的 Gralde 4.0 里程碑版本作为 gradle 的编译版本,该版本 gradle 编译速度有所加速,更加欣喜的是,完全支持 Java8。 当然,对于 Kotlin 的支持,在这个版本也有所体现,Kotlin 插件默认是安装的。

在 com.android.tools.build:gradle 3.0 以下版本依赖在 gradle 中的声明写法

compile fileTree(dir: 'libs', include: ['*.jar'])

但在 3.0 后的写法为

implementation fileTree(dir: 'libs', include: ['*.jar'])
或
api fileTree(dir: 'libs', include: ['*.jar'])

api 指令

完全等同于 compile 指令,没区别,你将所有的 compile 改成 api,完全没有错。

implement 指令

使用了该命令编译的依赖,它仅仅对当前的 Moudle 提供接口。 优点:1. 加快编译速度。2. 隐藏对外不必要的接口。

provided(compileOnly)

只在编译时有效,不会参与打包 可以在自己的 moudle 中使用该方式依赖一些比如 com.android.support,gson 这些使用者常用的库,避免冲突。

apk(runtimeOnly)

只在生成 apk 的时候参与打包,编译时不会参与,很少用。

testCompile(testImplementation)

testCompile 只在单元测试代码的编译以及最终打包测试 apk 时有效。

debugCompile(debugImplementation)

debugCompile 只在 debug 模式的编译和最终的 debug apk 打包时有效

releaseCompile(releaseImplementation)

Release compile 仅仅针对 Release 模式的编译和最终的 Release apk 打包。

# 查看那些库依赖了AndroidX

ref (opens new window)执行gradlew :app:dependencies

参考1 (opens new window)

# adb

adb shell ls /mnt/sdcard/

#如需从设备中复制某个文件或目录(及其子目录),请使用以下命令:
adb pull remote local

#如需将某个文件或目录(及其子目录)复制到设备,请使用以下命令:
adb push local remote

#将 local 和 remote 替换为开发机器(本地)和设备(远程)上的目标文件/目录的路径。例如:
adb push foo.txt /sdcard/foo.txt

# 防止休眠(搜索adb 防止息屏)
adb shell svc power stayon true

# 同时连接多个USB或模拟器设备的时候,指定对某个设备发出 adb 命令(7f1c864e是adb devices)
# 语法:adb [-d | -e | -s serial_number] command
adb -s emulator-5555 install helloWorld.apk

# 如果有多个设备,但只连接了一个USB硬件设备,请使用 -d 选项将命令发送至该硬件设备
adb -d install /Users/panda/wechat.apk
# 如果有多个可用设备,但只有一个是模拟器,请使用 -e 选项将命令发送至该模拟器
adb -e install /Users/panda/wechat.apk

https://developer.android.com/studio/command-line/adb#commandsummary

# adb通过 Wi-Fi 连接到设备(Android 10 及更低版本)

如果USB经常断开,可能是电压不足,请拔掉USB其他连接的设备,或者下面:👇

https://developer.android.com/studio/command-line/adb#wireless

一般情况下,adb 通过 USB 与设备进行通信,但您也可以在通过 USB 完成一些初始设置后,通过 WLAN 使用 adb,如下所述。不过,如果您开发的是 Wear OS 应用,则应参阅调试 Wear OS 应用 (opens new window)指南,其中提供了有关如何通过 WLAN 和蓝牙使用 adb 的特别说明。

  1. 将 Android 设备和 adb 主机连接到这两者都可以访问的同一 WLAN 网络。请注意,并非所有接入点都适用;您可能需要使用防火墙已正确配置为支持 adb 的接入点。

  2. 如果您要连接到 Wear OS 设备,请关闭手机上与该设备配对的蓝牙。

  3. 使用 USB 线将设备连接到主机。

  4. 设置目标设备以监听端口 5555 上的 TCP/IP 连接。

#第一步
adb tcpip 5555
  1. 拔掉连接目标设备的 USB 线。

  2. 找到 Android 设备的 IP 地址。例如,对于 Nexus 设备,您可以在设置 > 关于平板电脑(或关于手机)> 状态 > IP 地址下找到 IP 地址。或者,对于 Wear OS 设备,您可以在设置 > WLAN 设置 > 高级 > IP 地址下找到 IP 地址。

  3. 通过 IP 地址连接到设备。

#第二步,eg:adb connect 192.168.43.1
adb connect device_ip_address
  1. 确认主机已连接到目标设备:
$ adb devices -l
List of devices attached
device_ip_address:5555 device

现在,您可以开始操作了!

如果 adb 连接断开:

  1. 确保主机仍与 Android 设备连接到同一个 WLAN 网络。

  2. 通过再次执行 adb connect 步骤重新连接。

  3. 如果上述操作未解决问题,重置 adb 主机:

    adb kill-server
    

    然后,从头开始操作。

adb其他操作:

# 传送文件到手机SD卡下载目录
adb push pc.apk /mnt/sdcard/Download/test.apk

手机经常连上后息屏断开无法调试,adb devices没设备怎么办?

去手机设置里撤销USB授权、拔掉数据线、adb kill-server、adb start-server、插上数据线

adb: error: failed to get feature set: more than one device/emulator

➜ adb devices List of devices attached NBLDU21105003444 device 192.168.239.207:5555 device

➜ adb -s NBLDU21105003444 install /Users/panda/Documents/your_app.apk Performing Streamed Install Success

All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com/r/tools/flavorDimensions-missing-error-message.html

准备调试OKHTTPGO的时候遇到的,解决方法 (opens new window)

If you don't really need the mechanism, just specify a random flavor dimension in your build.gradle:

//最上面
apply plugin: 'com.android.library'

android { 
    //    compileSdkVersion 24
//    .....
    flavorDimensions "versionCode"
}

For more information, check the migration guide (opens new window)

# Android11以上无线调试

https://zhuanlan.zhihu.com/p/336660319

点击无线调试右边的开关

点击无线调试这一行

点击使用配对码配对设备 可以看到配对码、IP地址(比如192.168.43.102) 和端口号(比如37967)

IP和端口号每次点击都会生成新的

打开电脑输入:

# 注意,如果你的手机是电脑用的热点,那么把`使用配对码配对设备`里的IP换成电脑的路由器IP
adb pair 192.168.43.102:37967

# macOS的adb经常断开怎么办:

$ adb devices -l
List of devices attached

无解,我搞Android开发一半的时间浪费在List of devices attached,另外一半的时间浪费在Waiting For Debugger上

但是可以:

  • 不停切换USB连接模式,传输照片、传输文件、仅充电来回点击
  • 更新:brew install android-platform-tools

# TODO

sslSocketFactory

https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/ssl-socket-factory/

Trusting all certificates with okHttp 使用 okHttp 信任所有证书

https://stackoverflow.com/a/25992879

Okhttp 访问自签名证书 HTTPS 地址解决方案https://www.jianshu.com/p/cc7ae2f96b64

2019-07-16 Charles 在安卓7.0以上系统抓包辅助 https://www.jianshu.com/p/189ed03ec6e4

# 开源库

Android开源库的制作:https://juejin.cn/post/6844903630412201997

https://github.com/Blankj/AndroidUtilCode

简易获取用户位置 https://github.com/BirjuVachhani/locus-android/wiki

逆向:https://github.com/CodingGay/BlackDex

# 常见Error

Error: Activity class {com.topcheer.mlearning.uat/cn.com.spdb.mlearning.activity.SplashActivity} does not exist.

解决方法:

File - Sync Project with Gradle Files 或者右上角的大象🐘按钮

Failed to install the following Android SDK packages as some licences have not been accepted.

可能性1:一般发生在第一次使用Android Studio的时候

解决方案:

https://stackoverflow.com/a/61480578/4493393

In Android Studio go to Tools -> SDK Manager.

Go to SDK Tools tab.

Select the Android SDK Command-line Tools (latest) and download by pressing Apply.

可能性2:乱改包名造成新包名用旧的编译产物。

解决方案:改回包名或清理build缓存啥的

Failed to resolve: com.android.support:appcompat-v7:24.2.1 Add Google Maven repository and sync project

Error while executing: am start -n "cn.com.spdb.mlearning/cn.com.spdb.mlearning.activity.SplashActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -D Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=cn.com.spdb.mlearning/.activity.SplashActivity } Error type 3 Error: Activity class {cn.com.spdb.mlearning/cn.com.spdb.mlearning.activity.SplashActivity} does not exist.

Error while Launching activity

111

Could not identify launch activity: Default Activity not found Error while Launching activity

上次更新: 2/27/2022, 10:30:33 PM