Android App优化之ANR详解

App优化系列已近中期, 前面分享了一些工具, 理论, 也结合了案例谈了下启动优化, 布局分析等.

原计划将本文作为这个系列的一个承上启下点, 对前面几篇作一个小总结, 聊聊App流畅度和快速响应的话题.

粗一缕, 发现内容还是很多, 暂且拆成几篇来慢慢写吧, 勿怪~

今天先来聊聊ANR.

1, 你碰到ANR了吗

在App使用过程中, 你可能遇到过这样的情况:

%title插图%num
ANR

恭喜你, 这就是传说中的ANR.

1.1 何为ANR

ANR全名Application Not Responding, 也就是”应用无响应”. 当操作在一段时间内系统无法处理时, 系统层面会弹出上图那样的ANR对话框.

1.2 为什么会产生ANR

在Android里, App的响应能力是由Activity Manager和Window Manager系统服务来监控的. 通常在如下两种情况下会弹出ANR对话框:

  • 5s内无法响应用户输入事件(例如键盘输入, 触摸屏幕等).
  • BroadcastReceiver在10s内无法结束.

造成以上两种情况的首要原因就是在主线程(UI线程)里面做了太多的阻塞耗时操作, 例如文件读写, 数据库读写, 网络查询等等.

1.3 如何避免ANR

知道了ANR产生的原因, 那么想要避免ANR, 也就很简单了, 就一条规则:

不要在主线程(UI线程)里面做繁重的操作.

这里面实际上涉及到两个问题:

  1. 哪些地方是运行在主线程的?
  2. 不在主线程做, 在哪儿做?

稍后解答.

2, ANR分析

2.1 获取ANR产生的trace文件

ANR产生时, 系统会生成一个traces.txt的文件放在/data/anr/下. 可以通过adb命令将其导出到本地:

$adb pull data/anr/traces.txt .

2.2 分析traces.txt

2.2.1 普通阻塞导致的ANR

获取到的tracs.txt文件一般如下:

如下以GithubApp代码为例, 强行sleep thread产生的一个ANR.

  1. ----- pid 2976 at 2016-09-08 23:02:47 -----
  2. Cmd line: com.anly.githubapp // *新的ANR发生的进程(包名)
  3. ...
  4. DALVIK THREADS (41):
  5. "main" prio=5 tid=1 Sleeping
  6. | group="main" sCount=1 dsCount=0 obj=0x73467fa8 self=0x7fbf66c95000
  7. | sysTid=2976 nice=0 cgrp=default sched=0/0 handle=0x7fbf6a8953e0
  8. | state=S schedstat=( 0 0 0 ) utm=60 stm=37 core=1 HZ=100
  9. | stack=0x7ffff4ffd000-0x7ffff4fff000 stackSize=8MB
  10. | held mutexes=
  11. at java.lang.Thread.sleep!(Native method)
  12. - sleeping on <0x35fc9e33> (a java.lang.Object)
  13. at java.lang.Thread.sleep(Thread.java:1031)
  14. - locked <0x35fc9e33> (a java.lang.Object)
  15. at java.lang.Thread.sleep(Thread.java:985) // 主线程中sleep过长时间, 阻塞导致无响应.
  16. at com.tencent.bugly.crashreport.crash.c.l(BUGLY:258)
  17. - locked <@addr=0x12dadc70> (a com.tencent.bugly.crashreport.crash.c)
  18. at com.tencent.bugly.crashreport.CrashReport.testANRCrash(BUGLY:166) // 产生ANR的那个函数调用
  19. - locked <@addr=0x12d1e840> (a java.lang.Class<com.tencent.bugly.crashreport.CrashReport>)
  20. at com.anly.githubapp.common.wrapper.CrashHelper.testAnr(CrashHelper.java:23)
  21. at com.anly.githubapp.ui.module.main.MineFragment.onClick(MineFragment.java:80) // ANR的起点
  22. at com.anly.githubapp.ui.module.main.MineFragment_ViewBinding$2.doClick(MineFragment_ViewBinding.java:47)
  23. at butterknife.internal.DebouncingOnClickListener.onClick(DebouncingOnClickListener.java:22)
  24. at android.view.View.performClick(View.java:4780)
  25. at android.view.View$PerformClick.run(View.java:19866)
  26. at android.os.Handler.handleCallback(Handler.java:739)
  27. at android.os.Handler.dispatchMessage(Handler.java:95)
  28. at android.os.Looper.loop(Looper.java:135)
  29. at android.app.ActivityThread.main(ActivityThread.java:5254)
  30. at java.lang.reflect.Method.invoke!(Native method)
  31. at java.lang.reflect.Method.invoke(Method.java:372)
  32. at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
  33. at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

拿到trace信息, 一切好说.
如上trace信息中的添加的中文注释已基本说明了trace文件该怎么分析:

  1. 文件*上的即为*新产生的ANR的trace信息.
  2. 前面两行表明ANR发生的进程pid, 时间, 以及进程名字(包名).
  3. 寻找我们的代码点, 然后往前推, 看方法调用栈, 追溯到问题产生的根源.

以上的ANR trace是属于相对简单, 还有可能你并没有在主线程中做过于耗时的操作, 然而还是ANR了. 这就有可能是如下两种情况了:

2.2.2 CPU满负荷

这个时候你看到的trace信息可能会包含这样的信息:

  1. Process:com.anly.githubapp
  2. ...
  3. CPU usage from 3330ms to 814ms ago:
  4. 6% 178/system_server: 3.5% user + 1.4% kernel / faults: 86 minor 20 major
  5. 4.6% 2976/com.anly.githubapp: 0.7% user + 3.7% kernel /faults: 52 minor 19 major
  6. 0.9% 252/com.android.systemui: 0.9% user + 0% kernel
  7. ...
  8. 100%TOTAL: 5.9% user + 4.1% kernel + 89% iowait

*后一句表明了:

  1. 当是CPU占用100%, 满负荷了.
  2. 其中*大数是被iowait即I/O操作占用了.

此时分析方法调用栈, 一般来说会发现是方法中有频繁的文件读写或是数据库读写操作放在主线程来做了.

2.2.3 内存原因

其实内存原因有可能会导致ANR, 例如如果由于内存泄露, App可使用内存所剩无几, 我们点击按钮启动一个大图片作为背景的activity, 就可能会产生ANR, 这时trace信息可能是这样的:

  1. // 以下trace信息来自网络, 用来做个示例
  2. Cmdline: android.process.acore
  3. DALVIK THREADS:
  4. “main”prio=5 tid=3 VMWAIT
  5. |group=”main” sCount=1 dsCount=0 s=N obj=0x40026240self=0xbda8
  6. | sysTid=1815 nice=0 sched=0/0 cgrp=unknownhandle=-1344001376
  7. atdalvik.system.VMRuntime.trackExternalAllocation(NativeMethod)
  8. atandroid.graphics.Bitmap.nativeCreate(Native Method)
  9. atandroid.graphics.Bitmap.createBitmap(Bitmap.java:468)
  10. atandroid.view.View.buildDrawingCache(View.java:6324)
  11. atandroid.view.View.getDrawingCache(View.java:6178)
  12. MEMINFO in pid 1360 [android.process.acore] **
  13. native dalvik other total
  14. size: 17036 23111 N/A 40147
  15. allocated: 16484 20675 N/A 37159
  16. free: 296 2436 N/A 2732

可以看到free的内存已所剩无几.

当然这种情况可能更多的是会产生OOM的异常…

2.2 ANR的处理

针对三种不同的情况, 一般的处理情况如下

  1. 主线程阻塞的
    开辟单独的子线程来处理耗时阻塞事务.
  2. CPU满负荷, I/O阻塞的
    I/O阻塞一般来说就是文件读写或数据库操作执行在主线程了, 也可以通过开辟子线程的方式异步执行.
  3. 内存不够用的
    增大VM内存, 使用largeHeap属性, 排查内存泄露(这个在内存优化那篇细说吧)等.

3, 深入一点

没有人愿意在出问题之后去解决问题.
高手和新手的区别是, 高手知道怎么在一开始就避免问题的发生. 那么针对ANR这个问题, 我们需要做哪些层次的工作来避免其发生呢?

3.1 哪些地方是执行在主线程的

  1. Activity的所有生命周期回调都是执行在主线程的.
  2. Service默认是执行在主线程的.
  3. BroadcastReceiver的onReceive回调是执行在主线程的.
  4. 没有使用子线程的looper的Handler的handleMessage, post(Runnable)是执行在主线程的.
  5. AsyncTask的回调中除了doInBackground, 其他都是执行在主线程的.
  6. View的post(Runnable)是执行在主线程的.

3.2 使用子线程的方式有哪些

上面我们几乎一直在说, 避免ANR的方法就是在子线程中执行耗时阻塞操作. 那么在Android中有哪些方式可以让我们实现这一点呢.

3.2.1 启Thread方式

这个其实也是Java实现多线程的方式. 有两种实现方法, 继承Thread 或 实现Runnable接口:

继承Thread

  1. class PrimeThread extends Thread {
  2. long minPrime;
  3. PrimeThread(long minPrime) {
  4. this.minPrime = minPrime;
  5. }
  6. public void run() {
  7. // compute primes larger than minPrime
  8. . . .
  9. }
  10. }
  11. PrimeThread p = new PrimeThread(143);
  12. p.start();

实现Runnable接口

  1. class PrimeRun implements Runnable {
  2. long minPrime;
  3. PrimeRun(long minPrime) {
  4. this.minPrime = minPrime;
  5. }
  6. public void run() {
  7. // compute primes larger than minPrime
  8. . . .
  9. }
  10. }
  11. PrimeRun p = new PrimeRun(143);
  12. new Thread(p).start();

3.2.2 使用AsyncTask

这个是Android特有的方式, AsyncTask顾名思义, 就是异步任务的意思.

  1. private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
  2. // Do the long-running work in here
  3. // 执行在子线程
  4. protected Long doInBackground(URL... urls) {
  5. int count = urls.length;
  6. long totalSize = 0;
  7. for (int i = 0; i < count; i++) {
  8. totalSize += Downloader.downloadFile(urls[i]);
  9. publishProgress((int) ((i / (float) count) * 100));
  10. // Escape early if cancel() is called
  11. if (isCancelled()) break;
  12. }
  13. return totalSize;
  14. }
  15. // This is called each time you call publishProgress()
  16. // 执行在主线程
  17. protected void onProgressUpdate(Integer... progress) {
  18. setProgressPercent(progress[0]);
  19. }
  20. // This is called when doInBackground() is finished
  21. // 执行在主线程
  22. protected void onPostExecute(Long result) {
  23. showNotification("Downloaded " + result + " bytes");
  24. }
  25. }
  26. // 启动方式
  27. new DownloadFilesTask().execute(url1, url2, url3);

3.2.3 HandlerThread

Android中结合Handler和Thread的一种方式. 前面有云, 默认情况下Handler的handleMessage是执行在主线程的, 但是如果我给这个Handler传入了子线程的looper, handleMessage就会执行在这个子线程中的. HandlerThread正是这样的一个结合体:

  1. // 启动一个名为new_thread的子线程
  2. HandlerThread thread = new HandlerThread("new_thread");
  3. thread.start();
  4. // 取new_thread赋值给ServiceHandler
  5. private ServiceHandler mServiceHandler;
  6. mServiceLooper = thread.getLooper();
  7. mServiceHandler = new ServiceHandler(mServiceLooper);
  8. private final class ServiceHandler extends Handler {
  9. public ServiceHandler(Looper looper) {
  10. super(looper);
  11. }
  12. @Override
  13. public void handleMessage(Message msg) {
  14. // 此时handleMessage是运行在new_thread这个子线程中了.
  15. }
  16. }

3.2.4 IntentService

Service是运行在主线程的, 然而IntentService是运行在子线程的.
实际上IntentService就是实现了一个HandlerThread + ServiceHandler的模式.

以上HandlerThread的使用代码示例也就来自于IntentService源码.

3.2.5 Loader

Android 3.0引入的数据加载器, 可以在Activity/Fragment中使用. 支持异步加载数据, 并可监控数据源在数据发生变化时传递新结果. 常用的有CursorLoader, 用来加载数据库数据.

  1. // Prepare the loader. Either re-connect with an existing one,
  2. // or start a new one.
  3. // 使用LoaderManager来初始化Loader
  4. getLoaderManager().initLoader(0, null, this);
  5. //如果 ID 指定的加载器已存在,则将重复使用上次创建的加载器。
  6. //如果 ID 指定的加载器不存在,则 initLoader() 将触发 LoaderManager.LoaderCallbacks 方法 //onCreateLoader()。在此方法中,您可以实现代码以实例化并返回新加载器
  7. // 创建一个Loader
  8. public Loader<Cursor> onCreateLoader(int id, Bundle args) {
  9. // This is called when a new Loader needs to be created. This
  10. // sample only has one Loader, so we don't care about the ID.
  11. // First, pick the base URI to use depending on whether we are
  12. // currently filtering.
  13. Uri baseUri;
  14. if (mCurFilter != null) {
  15. baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
  16. Uri.encode(mCurFilter));
  17. } else {
  18. baseUri = Contacts.CONTENT_URI;
  19. }
  20. // Now create and return a CursorLoader that will take care of
  21. // creating a Cursor for the data being displayed.
  22. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
  23. + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
  24. + Contacts.DISPLAY_NAME + " != '' ))";
  25. return new CursorLoader(getActivity(), baseUri,
  26. CONTACTS_SUMMARY_PROJECTION, select, null,
  27. Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
  28. }
  29. // 加载完成
  30. public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
  31. // Swap the new cursor in. (The framework will take care of closing the
  32. // old cursor once we return.)
  33. mAdapter.swapCursor(data);
  34. }

具体请参看官网Loader介绍.

3.2.6 特别注意

使用Thread和HandlerThread时, 为了使效果更好, 建议设置Thread的优先级偏低一点:

  1. Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);

因为如果没有做任何优先级设置的话, 你创建的Thread默认和UI Thread是具有同样的优先级的, 你懂的. 同样的优先级的Thread, CPU调度上还是可能会阻塞掉你的UI Thread, 导致ANR的.

结语

对于ANR问题, 个人认为还是预防为主, 认清代码中的阻塞点, 善用线程. 同时形成良好的编程习惯, 要有MainThread和Worker Thread的概念的…

Android课程表App

*近写了个简单的Android 课程表App,我是个初学者,这个App里使用了:

Android内置的SQLite数据库储存课程数据。
课程的视图用CardView卡片视图。
课程的View是动态加入的,动态添加View的好处是很灵活

如果靠静态的XML构建的话就有点难扩展了,因为你不知道学生一天总共有多少节课

%title插图%num

下面的xml代码是课程表的布局了,一个LinearLayout表示课程表的左侧节数视图,七个Relative表示从星期一到星期天,然后根据用户的输入在正确的地方添加课程视图.代码有点长我没有全部贴出来,整个代码上面还有个父类ScrollView布局的,因为课程表可能有许多节课,是需要下拉的.

<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:orientation=”vertical”>

<!–工具条–>
<android.support.v7.widget.Toolbar
android:id=”@+id/toolbar”
android:layout_width=”match_parent”
android:layout_height=”?attr/actionBarSize”
android:background=”#7fab96c5″
app:theme=”@style/ThemeOverlay.AppCompat.Dark.ActionBar”
app:popupTheme=”@style/AlertDialog.AppCompat.Light”/>
<!–周–>
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”30dp”
android:background=”#7fab96c5″>

<TextView
android:layout_width=”110px”
android:layout_height=”match_parent”
android:gravity=”center”
android:text=”节/周”/>
<TextView
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:gravity=”center”
android:text=”周一”/>
<TextView
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:gravity=”center”
android:text=”周二”/>
<TextView
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:gravity=”center”
android:text=”周三”/>
<TextView
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:gravity=”center”
android:text=”周四”/>
<TextView
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:gravity=”center”
android:text=”周五”/>
<TextView
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:gravity=”center”
android:text=”周六”/>
<TextView
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:gravity=”center”
android:text=”周日”/>
</LinearLayout>
<!–课程表–>
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”wrap_content”>

<LinearLayout
android:id=”@+id/class_number_layout”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:orientation=”vertical”/>
<RelativeLayout
android:id=”@+id/monday”
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:orientation=”vertical”
android:layout_margin=”1dp”/>
<RelativeLayout
android:id=”@+id/tuesday”
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:orientation=”vertical”
android:layout_margin=”1dp”/>
<RelativeLayout
android:id=”@+id/wednesday”
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:orientation=”vertical”
android:layout_margin=”1dp”/>
<RelativeLayout
android:id=”@+id/thursday”
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:orientation=”vertical”
android:layout_margin=”1dp”/>
<RelativeLayout
android:id=”@+id/friday”
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:orientation=”vertical”
android:layout_margin=”1dp”/>
<RelativeLayout
android:id=”@+id/saturday”
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:orientation=”vertical”
android:layout_margin=”1dp”/>
<RelativeLayout
android:id=”@+id/weekday”
android:layout_width=”0dp”
android:layout_height=”match_parent”
android:layout_weight=”1″
android:orientation=”vertical”
android:layout_marginTop=”1dp”
android:layout_marginLeft=”1dp”
android:layout_marginBottom=”1dp”/>
</LinearLayout>
</LinearLayout>

课程的布局文件
<android.support.v7.widget.CardView
xmlns:android=”http://schemas.android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
android:layout_width=”45dp”
android:layout_height=”70dp”
app:cardBackgroundColor=”#7feacdd1″
app:cardCornerRadius=”6dp”>

<TextView
android:id=”@+id/text_view”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_gravity=”center”
android:gravity=”center”/>
</android.support.v7.widget.CardView>
这里的宽和高都是预览用的,因为实际的宽和高都是代码控制的.

创造视图的java代码
private void createLeftView(Course course) {
//动态生成课程表左侧的节数视图
int len = course.getEnd();
if (len > maxClassNumber) {
LinearLayout classNumberLayout = (LinearLayout) findViewById(R.id.class_number_layout);
View view;
TextView text;
for (int i = 0; i < len-maxClassNumber; i++) {
view = LayoutInflater.from(this).inflate(R.layout.class_number, null);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(110,180);
view.setLayoutParams(params);
text = view.findViewById(R.id.class_number_text);
text.setText(“” + number++);
classNumberLayout.addView(view);
}
maxClassNumber = len;
}
}

//创建卡片课程视图
private void createView(final Course course) {
int integer = course.getDay();
if ((integer < 1 && integer > 7) || course.getStart() > course.getEnd()) {
Toast.makeText(this, “星期几没写对,或课程结束时间比开始时间还早~~”, Toast.LENGTH_LONG).show();
} else {
switch (integer) {
case 1: day = (RelativeLayout) findViewById(R.id.monday);break;
case 2: day = (RelativeLayout) findViewById(R.id.tuesday);break;
case 3: day = (RelativeLayout) findViewById(R.id.wednesday);break;
case 4: day = (RelativeLayout) findViewById(R.id.thursday);break;
case 5: day = (RelativeLayout) findViewById(R.id.friday);break;
case 6: day = (RelativeLayout) findViewById(R.id.saturday);break;
case 7: day = (RelativeLayout) findViewById(R.id.weekday);break;
}
final View view = LayoutInflater.from(this).inflate(R.layout.course_card, null); //加载单个课程布局
view.setY(180 * (course.getStart()-1)); //设置开始高度,即第几节课开始
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams
(ViewGroup.LayoutParams.MATCH_PARENT,(course.getEnd()-course.getStart()+1)*180-2); //设置布局高度,即跨多少节课
view.setLayoutParams(params);
TextView text = view.findViewById(R.id.text_view);
text.setText(course.getCourseName() + “\n” + course.getTeacher() + “\n” + course.getClassRoom()); //显示课程名
day.addView(view);
//长按删除课程
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
view.setVisibility(View.GONE);//先隐藏
day.removeView(view);//再移除课程视图
SQLiteDatabase sqLiteDatabase = databaseHelper.getWritableDatabase();
sqLiteDatabase.execSQL(“delete from course where course_name = ?”, new String[] {course.getCourseName()});
return true;
}
});
}
}

我觉得核心的代码就这些了.

如何开发一个App(Android)

前言
本篇博客从开发的角度来介绍如何开发一个Android App,需要说明一点是,这里只是提供一个如何开发一个app的思路,并不会介绍很多技术上的细节,从整个大局去把握如何去构思一个app的开发,让你对独立开发一款app的时候有个理解,如果有说的不对的地方,欢迎大家拍砖留言讨论。

开发环境
Android应用层使用的开发语言是Java,自然需要用到Java运行环境,无论你在Window是系统还是Mac系统都需要安装JDK,并且配置它的环境变量,不懂什么叫环境变量的或者不知道怎么配的,请利用好自己的百度技能自行学习。

%title插图%num

开发工具
开发工具,Android因为历史原因,前面很长一段时间使用的是Eclipse,我们要开发Android应用的时候需要另外安装Google为它开发的插件ADT,Eclipse这个开源软件很优秀,也很专业,但仅仅只是使用它来开发Android就显得有点鸡肋了,如果没有对Eclipse进行优化的话,会很卡很卡,后面Google实在不愿意寄人篱下,就专门为我们开发者开发了目前很受广大开发者推崇的Android Studio,现在2.0预览版也出来啦,大伙赶紧去下吧。

%title插图%num

模拟器
Android自带的模拟器一直广受诟病,实在是太卡太慢了,对比人家IOS模拟器是那么的流畅,Android开发者直喊好苦逼啊,不过还好后面出了第三方比原生流畅n倍的模拟器Genymotion,大家可以去下一个个人版的够你平时开发测试用了。*好的办法还是使用真机测试,毕竟真机才是真实的环境。

%title插图%num

Android核心基础
前面是准备工作,想开发一款Android app,你*起码要掌握Android的核心基础知识,针对初学Android的童鞋,想一口吃掉一个胖子是不可能的,还是得把基础打牢之后才能完成独立的开发,Android入门不难,学完基础你只是具备开发app的基本条件,想做一个优秀的app还有很长一段路要走,经验是靠不断实践堆出来的,找一套系统的课程去认真学习一下,在线教育的资源很多,比如慕课网,*客学院都有很多不错的学习资源,童鞋自己择优来学习即可。

推广:http://edu.csdn.net/course/detail/545(笔者的课程)

产品开发流程
正常的互联网开发app的流程大致如下:
– 产品规划,定产品方向
– 需求调研,产出需求文档
– 需求评审,修订需求文档
– 产品狗画app线框图提供给射鸡师
– 射鸡师根据线框图设计视觉稿
– 程序猿根据视觉稿搭建UI框架
– 程序猿根据需求文档开发功能
– 测试媛编写测试用例,根据排期进行测试
– 程序猿修复回归测试反馈的bug,提交beta版
– 测试通过,提交给运营喵发布到渠道上线

上面是笔者的经验总结,可能有不太准确的地方,但大致整个流程是这样,我们开发者要清楚自己在整个产品中充当的角色,明白自己的工作职责即可。

快速搭建项目
Android比没有想象那么难,只是刚开始要学习的东西多,一下子消化不了才会比较茫然,笔者写这篇博客就是想帮助大家整理一下开发思路。

快速搭建项目也算是一项技能,而这项技能只能由你自己来完善,刚开始做开发的时候一定会有很多重复性的工作,如果没有意识去提高自己的开发效率的话,可能你的十年工作经验代表的只是一年的经验用了十年而已。

那要怎么做,笔者提供一个自己总结的,仅供参考:
– 定开发规范
– 搭建UI框架
– 选用开发库集成(或自造轮子)
– 第三方服务集成(视情况而定)

定开发规范
一个项目一般不会只有一个人独立开发,就算是独立开发,我们也应该定一下开发规范,一般会有什么样的规范?
– 命名规范
– 代码规范
– 代码管理规范

命名规范
命名规范包括:
– 项目命名
– 包命名
– 类命名、成员变量命名
– 资源文件命名
我们做每一个产品都会有相应的命名,不要用中文来命名,*好用能表达中文意思的英文来表示,例如CSDN客户端,可以用CSDNClient作为命名,我们创建项目的时候可以以它作为Application name。
可以看看以前写的这篇文章:
http://blog.csdn.net/wwj_748/article/details/42347283

代码规范
代码规范视语言而定,开发android使用的是Java,自然要按照Java的编码规范进行编码,比如命名采用驼峰命名法,编码的时候也要符合Android的开发规范,比如UI线程不做任何耗时的操作,像网络请求、数据库操作都需要放到子线程中去做,只有UI的刷新才在UI线程中做,像这些规范,可以根据自身在项目遇到的问题来定,定规范的好处就是减少踩坑的几率,提高开发的质量。

代码管理
对于一个经常更新迭代的产品,不可能由头到尾不变,这个时候我们需要对代码进行分支管理,*好使用git代码仓库对代码进行管理,作为一个合格的开发者连git都不用实在说不过去,还用svn的童鞋赶紧放弃它投入git的怀抱,它的好处是不言而喻的,自己去体会。

搭建UI框架
搭建UI框架需要我们根据产品的导航模式来设计,市场上常用的导航模式有如下图几种:

%title插图%num

我们的app如果不出意外一定是其中的一种导航模式,一般线框图出来我们就应该知道即将要开发的app长什么样子,开发者不必等视觉稿和素材出来才开始动工,我们先大致搭个架子,等视觉稿出来之后我们再做调整。

选用开发库
一般我们app涉及到的库会有:
– UI框架(比如下拉刷新PullToRefresh、侧滑菜单Slidingmenu)
– 网络请求库(比如okhtttp、AndroidAsyncHttp、Volley)
– 数据操作库(比如GreenDao、Ormlite)
– 图片缓存框架(比如Universal-Imageloader)
– 数据解析库(比如Gson)

之所以要选用这些库,肯定是为了避免重复造轮子,在开发效率的角度来说,选用优秀的开源库能大大缩短开发周期和提高开发效率,但从个人提升角度来看的话,我们可能就成了一个只会用API的程序猿了,如果想提升的话,造轮子或者分析这些优秀的源代码是一个不错的途径。

第三方服务集成
我们开发app的时候,肯定会遇到一些需求,比如推送的需求、自动升级、数据统计、社会化分享、用户反馈等等,然而对于一个刚起步的企业或者个人开发者的话,全都要自己去开发的话,那岂不是累死,像推送这种有一定的技术门槛,能做好都能成立一家公司了,所以选用一些第三方服务是一个可选之举。如果说你以后做大了,用第三方怕不好控制,那就自己做呗,有钱任性招兵买马就自己做,谁叫咱有钱呢。

前面这些东西开发一个app够了,开发出来能不能用还得有靠谱的测试,有没有crash,操作流不流畅,体验好不好才会有用户去用。这里不从产品的角度去评判一个app的好与坏,程序员要考虑的是从代码层面、性能层面去让我们的app变得更好。

云测
我们开发完毕之后,需要给测试工程师进行基本的功能需求测试,他们传统的做法就是根据事先写好的测试用例来做回归测试,再把测试出来的bug反馈给工程师,工程师再去修bug,但这样实在是太不靠谱了,有时候我们太在意功能而忽略了一些更重要的东西,那就是体验,给用户*直接的感受就是你这个app够不够惊艳,够不够流畅,用户可能根本就不在乎你这个功能做的有多牛逼。所以我们更应该从非功能性方向去做测试,我们的目的是让用户用的爽,而不是加一些乱七八糟的功能。那怎么测非功能性的一些因素,这里就要提到『云测』这个东西,因为现在设备太多了,如果公司要买一堆设备来做测试,那得多少成本,况且设备更新得太快,你根本就跟不上,所以就有了云测这个东西,它是一个云测试平台服务,提供了一大批主流机型,我们就直接省去购买设备的成本,还能得到完善的测试报告。

再来说一下它的好处:
– 终端云,省去测试设备购买租赁成本
– 高效率 节省测试人员成本及时间
– 包含兼容性测试、性能测试、功能测试
– 操作简单、详细测试报告生成

这么多好处,你在缺少测试工程师的时候,不去尝试那实在说不过去。

打包上线
前面的开发环节、测试环节都没问题之后,你离实现一个app的完整开发就不远了,正常的互联网公司,会把签名打包的apk给到运营,交给他们去写文案,上传到应用渠道,让渠道给我们去首发和推广。如果是个人开发者,这些工作就得我们自己做了。

总结
本篇博客从整个app开发流程上去给大家梳理了一遍,没有讲太多技术上的东西,但理解app流程对自己把握产品很有益处,虽然我们只是一个小小的开发者,但如果你有追求,哪天轮到你去负责一个产品的时候,你就应该对开发的每一个环节都了如指掌,因为出了问题,你也知道怎么针对性的去解决。笔者虽然只是一个小小的开发者,但也乐于思考,很多时候不太愿意被别人牵着鼻子走,因为我们的人生就应该把握在自己手里。

安卓有专业靠谱的数据恢复软件吗?

看了一下手机端的 app 基本都是鸡肋,稍微可信一点的一般都是 windows 上的软件,通过 usb 调试连接手机进行操作(借助 adb 和 root )。

看到一款 gihosoft android data recovery 好像有点可信,扫描文件也能扫描到很多。但是似乎只能恢复照片、视频、音乐、通讯录、短信、whatsapp 、txt 和 doc 之类的文档。难道没有基于整个存储介质的文件系统的任意文件的恢复吗?类似于电脑上的 diskgenius

7 条回复    2021-01-12 09:31:00 +08:00
zictos
    1

zictos   89 天前   ❤️ 2

已解决,root 后用 dd 命令把 /data 分区的所有内容保存为 img 镜像文件(如没有 dd 命令可通过安装 busybox 或者 termux 后获得)。
获得 img 文件后就可以用电脑上的专业数据恢复软件(比如 R-STUDIO 或 diskgenius )读取并恢复。

https://android.stackexchange.com/questions/208106/how-to-recover-a-deleted-file-from-data-partition

settoo
    2

settoo   89 天前 via Android

可惜 root 对华为手机来说就是没门
zictos
    3

zictos   89 天前

@settoo #2 淘宝上都可以解锁啊,具体可以搜“华为 root”
settoo
    4

settoo   89 天前 via Android

@zictos 记得去年我咨询过好多店,麒麟 970 以后都不能解锁,不知道今年第三方技术是不是进步
zictos
    5

zictos   89 天前

@settoo #4 我也不知道是否真的可以,具体你可以再咨询下
yysiman
    6

yysiman   88 天前

你都 root 为啥还整那么复杂,直接用 twrp 或者钛备份啊
zictos
    7

zictos   88 天前

@yysiman #6 是恢复已格式化的分区或者误删的文件

除了原厂的平台,安卓第三方 APP 市场有什么存在价值么?

应用宝、豌豆荚、360 手机助手之类,真的有人使用吗?什么用户群会使用那些市场? 那些市场分发效果怎么样?体验过百度手机助手,甚至分发数字都是假的,实际两三个下载居然显示 3000 多。

第 1 条附言  

补充一下,其实“原厂的平台”主要是想指好像很华为、小米、OV 、魅族等市场。
但是反而看到了许多关于 google play 的回复,
虽然的确是*“原厂”的平台,但是对于国内大众用户来说,还是比较“小众”吧?
做产品还是得多考虑普罗大众,v2ex 的 IT 界用户过多,可能存在幸存者偏差。
35 条回复    2021-01-15 14:33:04 +08:00
yunfeng17
    1

yunfeng17   85 天前 via iPhone

google play 永远是老大哥,其他没有意义
murmur
    2

murmur   85 天前

taptap 还是有点意义的,国内人那么多小众也不少人
murmur
    3

murmur   85 天前

另外自己做分发也是要准备一堆东西,尤其是 oss 都有黑洞机制,小架不住整一下更容易,本来开发就不容易因为 oss 被搞了进黑洞不是更郁闷

以前玩水宝宝,水弹 gun 论坛的 app 就是走应用宝分发,小众渠道能用,可能还水点,你说国内有几个渠道可以允许一个水弹 gun 论坛上架

ssynhtn
    4

ssynhtn   85 天前

应用宝的地位相当于微信内置浏览器
dnsaq
    5

dnsaq   85 天前

这些垃圾市场目的就是搞自己的那套封闭的东西,别人要上架要推广,好收钱。
WAHSUN
    6

WAHSUN   85 天前

@dnsaq 顺便还可以做个广告平台。
qiayue
    7

qiayue   85 天前   ❤️ 2

其实可以说是历史遗留问题,10 年前,厂商的官方市场大都很烂,才有了第三方市场的生存空间。
而且你不能拿*近几年的条件去跟 10 年前比,10 年前下载 app *常见的方式你知道是啥吗?
下载 apk,然后通过豌豆荚用 USB 传给手机。
哪像现在,手机直接连 WiFi 或者 4G 就下载了。
TypeError
    8

TypeError   85 天前 via Android

我就记得腾讯应用宝前两年一堆山寨 app
qiayue
    9

qiayue   85 天前

百度为啥愿意花钱去买 91 ?
阿里为啥愿意花钱去买豌豆荚?

放在当时的环境里,都是可以理解的,要怪就怪这个世界变化太快,当时的人们没有考虑到网速的变化会带来那么大的影响。
五六年前,你能相信大家不连 WiFi 也能流畅的刷视频?

Cheons
    10

Cheons   85 天前 via Android

@qiayue 5 、6 年前也是流量在线看视频,

zoharSoul
    11

zoharSoul   85 天前

因为豌豆荚之类的比华为 /vivo/oppo 这种应用市场出来的要早得多
px1396
    12

px1396   84 天前

我有使用 Google Play 的条件,但手机上还是安装了酷安, 里面的机老个个个都是人才,
wenjie83
    13

wenjie83   84 天前

@qiayue 56 年前 5g 已经普及了,得再往前推一推
580a388da131
    14

580a388da131   84 天前

华为的市场什么都没有,切换其它区也是什么都没有,只好装个酷安。
酷安算是转型成功了,黏性挺大,其它没用过。
Octopvs
    15

Octopvs   84 天前

@dnsaq 没有这些垃圾市场国内用户去哪下载,各个都*吗…
qiayue
    16

qiayue   84 天前

@Cheons @wenjie83 时间点记忆错误,不过不影响结论。
改成七八年前
imn1
    17

imn1   84 天前

在这问一个答案,出去问是另一个答案
Nokia 国行机的原厂应用市场是啥?
0o0o0
    18

0o0o0   84 天前 via Android

应用宝、豌豆荚、360 手机助手…
2021 了居然还有人用这些…
应用汇都比这几个强
不过因为前些年国内应用商店管控的严了很多,国内应用商店基本都已经失去了探索的意义,真正有趣又小众的软件这里不会出现。

主流 app 去 Google play 商店,国内一般应用可以用酷安解决,一些比较难找的可以去 Mobilism

有些人会推荐 apkpure 这类替代谷歌,但还是不推荐

lengyihan
    19

lengyihan   84 天前 via Android

酷安?做社区?或者是葫芦侠?做破解?
also24
    20

also24   84 天前

@qiayue #16
虽然算不上普遍现象。。。
但我确实 09 年就一直用手机流量看电视直播了。。
pengW
    21

pengW   84 天前 via Android

看不起酷安?评论区很强大好吧
treePerson
    22

treePerson   84 天前

@0o0o0 国外毋庸置疑,而在国内,你怎么看华为小米等市场?酷安的分发量和手机厂家的市场相比,不会很悬殊吗?
Benisme
    23

Benisme   84 天前

就我自己那个小 app 的感受来说,第三方市场基本没啥下载量,应用宝在其中算好一点,360 和百度可以比烂,豌豆荚卖给阿里后也辣鸡了。之所以还愿意维护这些第三方平台,纯粹是为了覆盖面广一点。
st2udio
    24

st2udio   84 天前

应用宝在腾讯的扶持下还是有量的。量*大的应该是华为。然后 oppo 、vivo 。然后是小米。酷安还是太小众。
kawaiidora
    25

kawaiidora   84 天前

下载一些原厂平台不上或不方便上的应用
zictos
    26

zictos   84 天前

应用宝用的人很多,很多时候一些 app 官网就提供了应用宝的下载链接。
酷安也不错,只是现在很多 app 搜不到了
qiayue
    27

qiayue   84 天前

@also24 有钱银。
我 09 年还用功能机。
JerryCha
    28

JerryCha   84 天前

国行 Xperia:什么是原厂平台?
ReferenceError
    29

ReferenceError   84 天前

( 1 )如果原装应用市场做得不好,比如三星(至少半年前我还在用三星),甚至做得更烂的其他厂商,那非小白用户肯定更愿意用第三方啊。
( 2 )原装应用市场可能收录 App 比较少,搜不到很烦
( 3 )就算应用还比较齐全,有些小众应用原装应用市场不上架
( 4 )第三方如果做得好,比如早期的豌豆荚(很久没用了不知道现在咋样),看看推荐的好玩应用也很有意思
flasktest1
    30

flasktest1   84 天前   ❤️ 2

f-droid 才是你永远的选择
felixlong
    31

felixlong   84 天前

@qiayue 真正的原因并不是网速问题。而是国内 android 手机市场已经被这头部四家全霸占完了。他们自然有了更多的话语权。
janus77
    32

janus77   84 天前

1 多一个分发渠道就多一份收益,反正没什么成本
2 小公司没法上多个渠道,只选择上主流渠道的
3 上古时候,手机自带渠道不完善的时候,只能去那里。然后后面就延续下来了
4 上古应用不再更新的,只有那里还可以保留下载
qiayue
    33

qiayue   84 天前

@felixlong 我解释的是当年为啥豌豆荚之类的第三方应用市场能做起来。不是解释后来为啥被厂商反超吊打。
PythonKGB
    34

PythonKGB   84 天前

我先帮你排除一个错误选项,百度应用市场。

这东西,有一段时间的下载量,是他们采集抓取的其余家应用市场下载量的 [加和] ,展示在自己的应用市场上。

后来被骂惨了,又改回去了。然后发布者用户看到的数据就是下载量暴增–下载量暴跌,根本解释不通。

suchbear
    35

suchbear   84 天前

豌豆荚 历史版本

有么有不用 PC 端就能 root 的 APP?

现在因测试需要,需要把手机 root,手机型号是小米 8,我网上找了一下,都是需要在 PC 断安装软件的,但这种软件我不敢往电脑上装。。就怕装了就卸载不了了

有没有 APP 能 root 的,因为手机是公司的测试用手机,不管啥 APP 都可以装

 

50 条回复    2021-02-14 22:27:53 +08:00
divilkcvf
    1

divilkcvf   57 天前   ❤️ 2

……无力吐槽
madao2015
    2

madao2015   57 天前   ❤️ 1

虚拟机
Microkernel
    3

Microkernel   57 天前

看来你对 Android 机应该怎么 root 还完全没概念。。给你两个方向自己查下吧 MIUI 开发版、Magisk
HarryQu
    4

HarryQu   57 天前

小米官方支持解锁 Bootloader,你可以自己刷开发版。
mgrddsj
    5

mgrddsj   57 天前   ❤️ 5

以前 Android 4 时代的那些一键 root (不管是需要 PC 端的还是不用 PC 端的)都是通过系统漏洞实现的,现在已经很少这样的系统漏洞了。而且那些一键 root (比如臭名昭著的 KingRoot )会在手机上安装很难删掉的流氓软件。
想 root 的话建议还是解锁 bootloader,刷 recovery,然后自己刷入 SuperSu/Magisk 之类的。这些教程应该在小米论坛 /XDA 不难找。不过都需要电脑,但电脑上不用装流氓软件。
mxT52CRuqR6o5
    6

mxT52CRuqR6o5   57 天前 via Android

你买台安卓 4 的手机就能无电脑 root 了
poic
    7

poic   57 天前   ❤️ 1

那就去网吧
bghtyu
    8

bghtyu   57 天前 via Android

先用小米的官方的解锁工具解锁 bootloader,然后用 Google 的 fastboot 工具刷一个 recovery,再把 magisk 刷进去就行了。有什么不敢往电脑上装的
no1xsyzy
    9

no1xsyzy   57 天前   ❤️ 1

你不敢往电脑上装的软件,为什么还敢让它去 root 手机?
怕电脑上卸载不了,就不怕手机上更卸载不了?
S179276SP
    10

S179276SP   57 天前

miui 开发版
katana97
    11

katana97   57 天前   ❤️ 1

pc 上要装的不就一个小米解锁工具和 Android sdk tools 么
QBugHunter
    12

QBugHunter   57 天前

@no1xsyzy
大佬。。。。我不说的很清楚么,手机是公司测试用的手机,随便搞
no1xsyzy
    13

no1xsyzy   57 天前

@QBugHunter 我觉得随便搞不包括流氓软件、挖矿软件、木马软件和可横向移动的蠕虫。
不然整个网络瘫痪。
3dwelcome
    14

3dwelcome   57 天前 via Android

不用 PC root 不可能,你可以用 VHD 系统,安装前备份系统,用完就恢复一下。比卸载干净多了。
XuanFei990
    15

XuanFei990   57 天前

@no1xsyzy 电脑是自己的电脑,手机是公司的手机,
zhuweiyou
    16

zhuweiyou   57 天前

“就怕装了就卸载不了了”

建议辞职

no1xsyzy
    17

no1xsyzy   57 天前

@XuanFei990 手机会连公司的网吧?小心把整个公司网搞瘫痪了
XuanFei990
    18

XuanFei990   57 天前

@no1xsyzy 楼主怕自己电脑中毒,不怕公司网络瘫痪,看来是无所谓。
QBugHunter
    19

QBugHunter   57 天前

@no1xsyzy

@XuanFei990

手机不会连公司网

marczhao
    20

marczhao   57 天前

1 、不要用任何“一键 root”软件。
2 、3 楼正解。
yolee599
    21

yolee599   57 天前 via Android

先解 bootloader 锁,装面具
ivyy
    22

ivyy   57 天前

有点意思,去网吧就是了
qqg1530
    23

qqg1530   57 天前 via Android

vmos pro
masker
    24

masker   57 天前 via Android

真可怜
oldshensheep
    25

oldshensheep   57 天前

电脑安装虚拟机,虚拟机中安装小米解锁工具就行了。一样的……,2 楼应该是这个意思。
Jirajine
    26

Jirajine   57 天前

正常来说 PC 端只需要安装安全的开源软件(android sdk platform-tools) 使用里面的 adb 和 fastboot 工具就足以。

但你这个是 OEM 需要,得用 OEM 提供的工具解锁。所以放到虚拟机里就可以,多数虚拟机对 USB 直通的支持都很好。

cat9life
    27

cat9life   57 天前   ❤️ 4

我居然在 V2 论坛发现这个个帖子 唉
fatelight
    28

fatelight   56 天前

真就不会搜一下,懒癌晚期
Hypixel
    29

Hypixel   56 天前 via Android

安装 App 来 root 和连接电脑用 root 工具的那个时代已经过去了,正确的做法是:
1. 用小米官方解锁工具解锁 Bootloader (需要绑定账号,可能会有 15 天不等的限制时间)
2. 刷入 TWRP 和 Magisk 或者刷入 MIUI 开发版并开启 root
systemcall
    30

systemcall   56 天前

@Jirajine #26
Hyper-V 就不支持 USB 直通
cofface
    31

cofface   56 天前

自己动手就解 BL,刷 twrp recovery,刷 magisk root 包完毕。
learningman
    32

learningman   56 天前

@systemcall 支持啊。。。至少 WSL2 可以,WSL2 应该是基于 Hyper-V 的吧
systemcall
    33

systemcall   56 天前

@learningman #32
没看到原生的 USB 重定向支持,只找到用 USB over IP 来弄的。那个东西估计还不如 RDP 的远程 USB 重定向
Hyper-V 感觉可以用 DDA 分配一个 USB 扩展卡来给里面的虚拟机弄出来 USB 接口,没试过。我的 b450m 迫击炮实际上只有 1 个 PCIe X1,插了这个的话就不能够插无线网卡了。当时买的时候考虑到了这个问题,但是这个价位的板子那个时候都差不多寒酸
感觉这方面 Hyper-V 还是没有 Virtualbox 功能全,后者哪怕连 VT-d 之类的 I/O 虚拟化都不支持,也还是可以分配 USB 设备和存储设备
GeruzoniAnsasu
    34

GeruzoniAnsasu   56 天前

公司给你一个 andorid 手机让你“随便搞”
然后 你吧 android sdk 也不用装,adb bridge 也不用装,fastboot bridge 也不用
给你这个手机自拍用的?
JBaker
    35

JBaker   56 天前

我很好奇,明明每年 Android 都公开了那么多的 CVE,linux 内核也有一大堆 CVE,为啥就没有几个“一键 root”跟进更新出来呢?为啥全都是(相对手机一键 root)巨麻烦的“解锁+刷机”的流程呢?。。。
JensenQian
    36

JensenQian   56 天前

1 小米解 bl 锁 https://www.miui.com/unlock/index.html 这边申请,现在*新版本的可能要绑定 7 天,旧版本的 miui 应该不需要
2 具体咋样,网上有详细过程的
3 然后刷 twrp,https://twrp.me/xiaomi/xiaomimi8.html 刷进去,国内定制的 twrp 你可以搜下 wzsx150 这个
cev2
    37

cev2   56 天前

@JBaker 35# 1.且不说那一大堆 Linux 的漏洞中可用于提权的漏洞只有 1 成左右,这 1 成可用于提权的漏洞又有大多数是 KVM 、XEN 、网络相关的部分,而这其中大部分安卓裁剪掉了,并没用到相关模块,可利用的其实不多。
2.BL 锁的存在,目前的机型如果无法解锁 BL,即使通过漏洞 [临时] 获取了 root 权限,也无法修改 /system 分区文件,强行修改后会导致下次系统无法启动。只修改 /data 分区多数是可以的,但大多数使用一键 root 的用户才不会去仔细看使用说明,对他们来说 root 后怎么能不精简个系统 APP ?一精简开不了机了。。。对于开发者来说,这种“不完美的 root”费力不讨好,不适合拿来做一键 root 。
3.如果通过漏洞破解掉了 BL 锁,那就没有了 2 的限制,但这又回到了你的问题,既然已经解了 BL 锁了,*简单通用的方式当然是直接刷入 magisk 。
以上,不解 BL 锁无法完美永久的 root,解了 BL 锁没必要再研究其他的 root 方式。(仅针对大多数通用情况,不排除小部分厂商 BL 不限制 /system 修改后启动,不具备实用意义)
WebKit
    38

WebKit   56 天前 via Android

root 本来也不用装什么软件啊。。。adb 有这个环境就行。其他的都是命令操作的脚本
w950888
    39

w950888   56 天前

你为什么会认为解锁软件卸不掉? 毫无理由的黑小米.
QBugHunter
    40

QBugHunter   56 天前

@GeruzoniAnsasu
这 3 个装了哪个能抓包?
GeruzoniAnsasu
    41

GeruzoniAnsasu   56 天前

@QBugHunter 你要 tcpdump 为什么不直接连局域网然后在网关上抓?
QBugHunter
    42

QBugHunter   56 天前

@GeruzoniAnsasu
你猜?
codehz
    43

codehz   56 天前

@JBaker #35 因为谷歌每月发布安全更新(
codehz
    44

codehz   56 天前

如果 Android 10 没有启用 seccomp,有一个一键 root 的方法 https://github.com/vvb2060/Magica
arvinsilm
    45

arvinsilm   56 天前

建议买台电脑,单拉网线,物理隔离
Zy143L
    46

Zy143L   55 天前 via Android

这楼主是活在安卓 2.3.6 还是 4.0 的时代?
小米 8 的话 去解锁手机 小米官方有解锁工具
然后 Magisk 随便刷..有啥不放心的
lychs1998
    47

lychs1998   55 天前

测试为啥需要 root…
YamatoRyou
    48

YamatoRyou   55 天前

*近入了一个小米 9T, 获取 Root 权限确实不需要在 PC 上完成 (通常是已有 TWRP 的情况下卡刷 Magisk), 但解锁 Bootloader 和刷 TWRP 必须在 PC 上完成 (如果曾经没有解锁的话).
kingfalse
    49

kingfalse   54 天前 via Android

去网吧那兄弟成功逗笑我了
acess
    50

acess   51 天前

按照标准的流程,解锁 bootloader 是需要恢复出厂清空数据的。

安卓不询问用户,默认授予位置权限

刚打开一个很久没用过的软件,弹了个什么权限,懒得看就点了拒*,然后看到状态栏有定位提示。设置里发现自动授予了位置权限,
清除数据(权限会被重置)又试了几次,*次打开时只会问管理通话和访问文件权限(都拒*了),然后再打开设置已经自动允许定位了……
正常设计不是未显式同意一律拒*吗,谷歌这样搞图个啥?有办法更改默认权限吗?安卓 10

27 条回复 • 2021-04-02 15:30:51 +08:00
qq73666 1
qq73666 5 小时 47 分钟前 ❤️ 3
几乎不可能默认授权,如果默认授权何必询问你要授权??*大可能是你点错了,无知不是信口开河的理由
S179276SP 2
S179276SP 5 小时 46 分钟前 via Android
什么手机?
alfchin 3
alfchin 5 小时 44 分钟前 via Android
MIUI+迪士尼救国?
LaTero 4
LaTero 5 小时 43 分钟前 via Android
@qq73666 刚又试了两次,*对没有点错,而且你没看清正文吧,它根本没问我要过位置的授权,只要过电话和储存。
@S179276SP 一加 7pro,LineageOS
app 是闲鱼,一年没用,看到卖 2080super 的帖子就上去看了一下
rosu 5
rosu 5 小时 38 分钟前 via Android
位置是危险权限。只可能是你的使用版本的 LOS 有 bug 。你可以直接试试其他软件就知道了。
littiefish 6
littiefish 5 小时 35 分钟前 via iPhone
哪个软件啊,说出来让大家避坑
xmumiffy 7
xmumiffy 5 小时 35 分钟前 via Android
应用 target 23- 就没动态权限,安装时默认给所有权限
LaTero 8
LaTero 5 小时 21 分钟前 via Android
@rosu 试了 alipay,绿色聊天软件 play 版( vx,不让发),但是它们都不会一启动就要位置权限,不确定到底是不是 bug,放假我更新一下系统吧。

@xmumiffy 但是它会问我电话和储存的权限……
HongJay 9
HongJay 5 小时 2 分钟前
软件越过系统吗。。
nashxk 10
nashxk 4 小时 44 分钟前
刚刚下载试了一下拒*后不会获得定位权限,包里写的 targetSdkVersion 是 28 。app 没更新?或者是 os 有问题?我的是 pixel3a,Android11

stephenxiaxy 11
stephenxiaxy 4 小时 38 分钟前
我玩 flutter 的时候,有两种方式,一种是代码里询问,另一种是直接写配置文件,第二种安装的时候是不需要询问的,默认已经有了
LaTero 12
LaTero 4 小时 21 分钟前 via Android
@nashxk 并没有拒*,因为它根本没有问…拒*的是电话和储存,位置没有问直接默认允许了。
dingwen07 13
dingwen07 1 小时 31 分钟前 via iPhone
把软件的目标 API 版本发出来
acrisliu 14
acrisliu 1 小时 29 分钟前 via Android
卸载重装试试,我的 Oxygen OS 清除数据不会重置权限。
AoEiuV020 15
AoEiuV020 1 小时 27 分钟前
感觉只能是 targetSdkVersion 太低,
zhangjiafan 16
zhangjiafan 1 小时 14 分钟前
targetSdkVersion 太低
LaTero 17
LaTero 58 分钟前 via Android
@dingwen07
@AoEiuV020
@zhangjiafan api 版本 26,试了另一个 api 版本 25 的地图程序也不会这样。而且 api 版本低就能偷权限不是可以被恶意利用吗
AoEiuV020 18
AoEiuV020 43 分钟前
@LaTero 建议把 apk 发出来大伙学习学习,
AoEiuV020 19
AoEiuV020 40 分钟前
@LaTero api 版本低就能偷权限这是没办法的,targetSdkVersion 低的典型情况是 app 开发时新版本的安卓根本没开发出来,人家当然没法遵守新版本的安全新机制,只是反过来让新版本安卓系统去兼容这些老 app,
而且也没想的那么严重,当年权限都是安装时展示出来安装等于授权,也用了很多年了,安全性不够高但也不是太值得担忧,
AoEiuV020 20
AoEiuV020 36 分钟前
@AoEiuV020 看到上面说的闲鱼了,我 miui12 刚试了下正常,有普通的弹出权限请求,
AoEiuV020 21
AoEiuV020 35 分钟前
还是建议把 apk 发出来看看,感觉用的不是一个东西,我试了下闲鱼*次打开就请求了定位,没有请求楼主说的“*次打开时只会问管理通话和访问文件权限”,
toptyloo 22
toptyloo 32 分钟前 via Android
@LaTero api 版本低商店会不让上架,然后机器能不能装上也不一定。
LaTero 23
LaTero 29 分钟前 via Android
@AoEiuV020 就是闲鱼,版本好像是 6.8.x,很久没更新了,今天看到个卖 2080s 的帖才打开看了下,现在更新到了 api28,会问位置权限了。
安卓 10 位置要手动开其实也不算特别不安全吧,只是我试了几个 lollipop 和 nougat 的程序都不会这样,感觉挺奇怪
LaTero 24
LaTero 26 分钟前 via Android
@AoEiuV020 打字慢了点,我没度盘账号,不知道咋发,版本 6.4.10
AoEiuV020 25
AoEiuV020 22 分钟前
@LaTero 临时文件我一般传到免注册的网站上,比如奶牛,
https://cowtransfer.com/
LaTero 26
LaTero 20 分钟前 via Android
@AoEiuV020 找了个国外共享盘:aHR0cHM6Ly91ZmlsZS5pby96YWswZnRmZA==(不让我发链接),又卸载重装了一次完美复现,安卓 10,安全补丁 2020 年 9 月 5 日
AoEiuV020 27
AoEiuV020 7 分钟前
@LaTero 包没发现什么特别的,只能怀疑是你的 los 有什么问题了,指不定是 rom 作者加了什么私料,我试了 miui 安卓 10 和魔趣安卓 9 都是正常申请权限。

请推荐一款可以远程控制 Android 手机的 app?

需要在 Android 上跑一个软件,但是又不想天天带着一个额外的手机,因此想 Android 手机放在家里一直连着网络,然后外出的时候通过网络链接,*好可以在手机上远程控制,比如打开 App 等操作。

请问有没有哪位使用过类似的 App 并且觉得体验不错的?希望推荐一下。谢谢。

第 1 条附言  

谢谢各位,scrcpy 是个很不错的选项,但是似乎只支持 Mac/Windows/Linux,而我也不想随身带一台电脑
27 条回复    2021-04-03 11:48:58 +08:00
zilaijuan
    1

zilaijuan   5 天前 via Android

AirMirror
opentrade
    2

opentrade   5 天前 via Android

跑个模拟器不行吗
hgjian
    3

hgjian   5 天前 via Android

向日葵付费版
imdong
    4

imdong   5 天前 via iPhone   ❤️ 1

Airdroid. 远控需要 root,如果能连接电脑,似乎方案就多了起来
heyenyan
    5

heyenyan   5 天前 via Android

scrcpy 连接家里电脑,电脑安装向日葵。就可以既远控手机也远控电脑了
StatFs
    6

StatFs   5 天前

5 楼正解
c1123717713l
    7

c1123717713l   5 天前

AnyDesk 无论是在电脑还是在手机上都可以控制别的设备
moguanqi
    8

moguanqi   5 天前

ToDesk 不知道手机远程手机行不
kingiis
    9

kingiis   5 天前

有一点安全风险
asdjgfr
    10

asdjgfr   5 天前

我有类似的需求,*后的解决办法就是 被控制的电脑 wol 开机,然后用 frp 远程控制,*后用 scrcpy 控制手机,目前没找到其他更好的解决办法

crescentBLADE
    11

crescentBLADE   5 天前   ❤️ 1

jinsongzhao
    12

jinsongzhao   5 天前

华为家长助手!
Ciicing
    13

Ciicing   5 天前

teamview 就可以,手机上要安装一个插件,去 google 商店就可以。远程解锁也是没有问题的,但是限制型号,比如华为这样的大哥就不可以。你可以搜搜看看。不过我怀疑你想远程打卡用。
SuCaiJianDan
    14

SuCaiJianDan   5 天前

teamviewer
iterry7758
    15

iterry7758   5 天前

是不是又要偷鸡下班打卡了?
ares586
    16

ares586   5 天前

AirDroid
我测试过很多,这个*好用。有流量限制。
andyskaura
    17

andyskaura   5 天前

@Ciicing 卧槽 可以啊
Ciicing
    18

Ciicing   5 天前

@andyskaura 你是说远程打卡可以啊 是吗?那你不如买个树莓派装上安卓,打开定位和钉钉,钉钉设置自动打卡,一直接着电源就成了
zhouweiluan
    19

zhouweiluan   5 天前

AirDroid 搭配 AirMirror,免 Root
ilxv
    20

ilxv   4 天前

@Ciicing hhh 楼主被抓了
Ciicing
    21

Ciicing   4 天前

@ilxv 我感觉我好像给好多人提供了灵感了。
CurChen
    22

CurChen   4 天前

@Ciicing bingo
Ciicing
    23

Ciicing   4 天前

-_-||
fengmumu
    24

fengmumu   4 天前

@Ciicing 不是说会看熄屏这些的吗?
Ciicing
    25

Ciicing   4 天前

@fengmumu 远程打卡是可以控制手机的,可以亮屏解锁,如果用树莓派的话,那还息屏啥,接一次显示器设置好了,吧显示器拔了就行了
fengmumu
    26

fengmumu   4 天前

@Ciicing 666

Android App 软件架构的特征

什么是架构?我*初的理解,架构就是通过降低偶合性,提高安全性和扩展性,达到方便对软件进行维护的一套行之有效的分层思想。在我看来架构*主要的就是降低偶合性和提高扩展性,我们平常对于客户端的修改和重构也基本上是围绕这两个点而进行的。当然,这只是我个人的理解,为了怕自己理解太过片面,又在以下几个权威网站,看了一下他们对架构的解释:

百度百科:软件架构(software architecture),是一系列相关的抽象模式,用于指导大型软件系统各个方面的设计。软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件,各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。在面向对象领域中,组件之间的连接通常用接口(计算机科学)来实现。

维基百科:软件架构是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计。
某位技术大牛:软件架构就是软件的基本结构。

其实总体说来,软件架构就是软件的基本结构,百度和维基说到抽象描述也基本上是这个道理。

%title插图%num

软件架构特征

一个好的软件架构又基本上具备以下的特征:
维护性:一个好的软件肯定是可以方便维护的,出了问题可以快速排查并修改,不会一改这个问题,又暴出另外的问题。
耦合性:这个是我开发Android过程中体会*多的一个特性,开发过程中有看到一些类,同其它类的耦合性过于强,如果想改一个地方,需要考虑另一个类会不会受到影响,导致自己在做修改的时候总是提心吊胆。还有就是耦合性过强不利于快速定位问题所在,总得来说就是“做人要独立,不要过于依赖”。
扩展性:一个好的架构一定是很方便扩展的,既不要因为扩展而影响到其它,也尽量不要过多从其它地儿复制出代码,如果很多地方都会用到这些代码可以封闭出一个类。

总结

当然,构架的特性还有很多,但基本上都是说大型架构的,在平常Android客户端开发中,*常用、体会*深的还是这几个特性。客户端比较常用的几个架构,比如MVC、MVP也基本是围绕“降低偶合性,提高扩展性和维护性”进行设计开发的。