Service使用笔记

看过好几回了,但是还是好像每次都忘,可能是用得少,但是工作中看的一些源代码中用的太多了,可是每次用的时候都看一遍,有点浪费时间,还是自己做一个简单的总结,这样可能以后会快一些。

这篇在草稿箱里实在是存太久了。。。


Service的分类

  • 启动型
    通过调用startService() 启动,一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。

  • 绑定型
    通过调用bindService() 绑定到服务,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行; 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁

服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。

Service的生命周期

因为其分两类,所以它的生命周期也有两种:
图片1

左边为启动型, 右边为绑定型.

Service在清单文件中的属性

  • android:exported: 顾名思义,是否可以被导出,即是否可以被绑定用做服务端.
    false:确保服务仅适用于您的应用.
    true: 与false相反.

请始终使用显式 Intent 启动或绑定 Service,且不要为服务声明 Intent 过滤器

启动型Service

继承Service

若使用这种方法,则需要注意一个问题,那就是**Service默认的线程是主线程**, 不要在其中做耗时的事情.

继承IntentService

使用这种方法, 就不需要担心线程的问题.因为其做为一个对Service的简单封装,它内部已经将所有的事件都放在了子线程中.不妨对其做一个简单的分析来加深对Service的理解.

1
2
3
4
5
6
7
8
9
10
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread =
new HandlerThread("IntentService[" + mName + "]");
thread.start();

mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}

在其onCreate()中,将开启一个初始化好Looper的子线程. 并为这个Looper的消息队列添加一个Handler.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//IntentService.java
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}

@WorkerThread
protected abstract void onHandleIntent(
@Nullable Intent intent);

其中的Handler用来处理在这个子线程中的事务,其中的onHandleIntent()正是我们在继承IntentService时所需要实现的一个方法, 也就是说我们在其中写的方法都是在子线程中执行.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}

/**
* You should not override this method for your IntentService. Instead,
* override {@link #onHandleIntent}, which the system calls when the IntentService
* receives a start request.
* @see android.app.Service#onStartCommand
*/
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

onStart()并没有太大的意义,只是一个普通的函数,重要的是它里面做的事情。它将从onStartCommand()里面得到的intent,通过message传递给handler,然后再是自己,对不同intent的处理。

onStartCommand()的返回值,有下面三种:

  • START_NOT_STICKY
    如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
  • START_STICKY
    如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。
  • START_REDELIVER_INTENT
    如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。

如何停止

启动服务必须管理自己的生命周期。也就是说,除非系统必须回收内存资源,否则系统不会停止或销毁服务,而且服务在 onStartCommand() 返回后会继续运行。
因此,服务必须通过调用stopSelf() 自行停止运行,或者由另一个组件通过调用 stopService() 来停止它。一旦请求使用 stopSelf() 或 stopService() 停止服务,系统就会尽快销毁服务。

http://blog.csdn.net/baidu_31405631/article/details/52469093

绑定型Service

对于此种类型的,感觉自己用得到的地方并不是很多,但是要能够看得懂这这种类型的Service的代码,尤其是AIDL,在Android中非常常见。

AIDL的大致使用步骤如下:

  1. 编写包含接口的aidl文件,make project,生成同名的java文件
  2. AIDL是一种可跨进程的,就是说可以在进程A中,调用进程B中的方法,所以要首先要实现该方法类。实现其中的方法,只需要继承同名java文件中的Stub类,并实现其中的方法,即可。

ButterKnife官方使用指南

看到了ButterKnife之后,感觉它实在是太棒了,可以省略掉一大堆无趣的findViewById(),整个代码看起来都舒服多了。这篇使用说明来自它的官方网站的简易介绍,用起来非常简单,但是也是有挺多的情况,所以还是觉得自己翻译出来,方便以后查阅吧!


使用@BindViewID注解相应的变量,ButterKnife就会在你的layout文件中找到所对应的View并赋值给它。

1
2
3
4
5
6
7
8
9
10
11
12
class ExampleActivity extends Activity {
@BindView(R.id.title) TextView title;
@BindView(R.id.subtitle) TextView subtitle;
@BindView(R.id.footer) TextView footer;

@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}

上面例子中,所生成的代码大致与下面代码等同:

1
2
3
4
5
public void bind(ExampleActivity activity) {
activity.subtitle = (android.widget.TextView) activity.findViewById(2130968578);
activity.footer = (android.widget.TextView) activity.findViewById(2130968579);
activity.title = (android.widget.TextView) activity.findViewById(2130968577);
}

资源绑定

使用@BindBool, @BindColor, @BindDimen, @BindDrawable, @BindInt, @BindString与一个对应的ID来绑定定义好的资源,

1
2
3
4
5
6
7
class ExampleActivity extends Activity {
@BindString(R.string.title) String title;
@BindDrawable(R.drawable.graphic) Drawable graphic;
@BindColor(R.color.red) int red; // int or ColorStateList field
@BindDimen(R.dimen.spacer) Float spacer; // int (for pixel size) or float (for exact value) field
// ...
}

非ACTIVITY绑定

我们还可以在已知View的情况下,在任意的对象中,绑定该View中所含有的控件。比如在Fragment中:

1
2
3
4
5
6
7
8
9
10
11
public class FancyFragment extends Fragment {
@BindView(R.id.button1) Button button1;
@BindView(R.id.button2) Button button2;

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
}

另外一个是在ViewHolder中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MyAdapter extends BaseAdapter {
@Override public View getView(int position, View view, ViewGroup parent) {
ViewHolder holder;
if (view != null) {
holder = (ViewHolder) view.getTag();
} else {
view = inflater.inflate(R.layout.whatever, parent, false);
holder = new ViewHolder(view);
view.setTag(holder);
}

holder.name.setText("John Doe");
// etc...

return view;
}

static class ViewHolder {
@BindView(R.id.title) TextView name;
@BindView(R.id.job_title) TextView jobTitle;

public ViewHolder(View view) {
ButterKnife.bind(this, view);
}
}
}

其它的绑定方式

可使用Activity当做一个根View可以绑定任何对象。如果你使用了MVC模式,你可以使用ButterKnife.bind(this, activity)来绑定Controller
可使用ButterKnife.bind(this)来绑定一个View里面的子View。如果你在layout文件中使用了<merge>标签并且在View的构造器中填充,你可以在这之后立马调用它。或者,你也可以在onFinishInflate()回调中调用。

VIEW LISTS

将所需要的控件,全部填充到一个List中。

1
2
@BindViews({ R.id.first_name, R.id.middle_name, R.id.last_name })
List<EditText> nameViews;

apply()方法可以对List中所有的View执行某个操作。

1
2
ButterKnife.apply(nameViews, DISABLE);
ButterKnife.apply(nameViews, ENABLED, false);

可以指定一些简单的动作。

1
2
3
4
5
6
7
8
9
10
static final ButterKnife.Action<View> DISABLE = new ButterKnife.Action<View>() {
@Override public void apply(View view, int index) {
view.setEnabled(false);
}
};
static final ButterKnife.Setter<View, Boolean> ENABLED = new ButterKnife.Setter<View, Boolean>() {
@Override public void set(View view, Boolean value, int index) {
view.setEnabled(value);
}
};

当然也可以在apply()方法中指定一个Android中控件的属性名。

ButterKnife.apply(nameViews, View.ALPHA, 0.0f);

绑定LISTENER

监听器也可以自动配置到相应的View上。

1
2
3
4
@OnClick(R.id.submit)
public void submit(View view) {
// TODO submit data to server...
}

监听器函数的参数都是可选的。

1
2
3
4
@OnClick(R.id.submit)
public void submit() {
// TODO submit data to server...
}

指定一个确定的类型,它将会被自动转换成之。

1
2
3
4
@OnClick(R.id.submit)
public void sayHi(Button button) {
button.setText("Hello!");
}

还可以为将一个监听器函数,绑定到多个控件上。

1
2
3
4
5
6
7
8
@OnClick({ R.id.door1, R.id.door2, R.id.door3 })
public void pickDoor(DoorView door) {
if (door.hasPrizeBehind()) {
Toast.makeText(this, "You win!", LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Try again", LENGTH_SHORT).show();
}
}

自定义View时,绑定自己的监听器函数不需要设置ID

1
2
3
4
5
6
public class FancyButton extends Button {
@OnClick
public void onClick() {
// TODO do something!
}
}

重置绑定

FragmentActivity的生命周期不同。当在FragmentonCreateView()中使用了绑定,就需要在onDestroyView()中将变量置为nullButterKnife在调用绑定时会返回一个Unbinder的实例,在适当的生命周期回调中,调用这个实例的unbind()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FancyFragment extends Fragment {
@BindView(R.id.button1) Button button1;
@BindView(R.id.button2) Button button2;
private Unbinder unbinder;

@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
unbinder = ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}

@Override public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}

其它绑定

通常@Bind和监听器绑定都是必须的。如果在目标View中为找到相应ID的控件,则会抛出异常。

为了抑制住这中异常,创建一个可选的绑定,可以使用@Nullable@Optional来注解变量或方法。

注 : 可使用任何名为@Nullable的注解来注解变量,但推荐Android support-annotations中的@Nullable

1
2
3
4
5
@Nullable @BindView(R.id.might_not_be_there) TextView mightNotBeThere;

@Optional @OnClick(R.id.maybe_missing) void onMaybeMissingClicked() {
// TODO ...
}

多方法监听器

可在注解中加入参数来区分。

1
2
3
4
5
6
7
8
9
@OnItemSelected(R.id.list_view)
void onItemSelected(int position) {
// TODO ...
}

@OnItemSelected(value = R.id.maybe_missing, callback = NOTHING_SELECTED)
void onNothingSelected() {
// TODO ...
}

下载

GRADLE

1
2
compile 'com.jakewharton:butterknife:(insert latest version)'
annotationProcessor 'com.jakewharton:butterknife-compiler:(insert latest version)'

通过反射来获取Intent中的Key

在项目中遇到一种情况,我想通过了解某个Intent里面到底存了哪些数据来解决这个问题。但是我们知道,Intent里面的数据需要知道key才能调用相应的函数取出来,所以如何才能找出这些key呢?通过对Intent代码里面对存数据的观察,我们可以看到,通过putExtra()存的数据,都放在一个叫做mE
阅读更多

系统应用闹钟之TabLayout的使用

此篇文章记录一些关于安卓原生应用闹钟里面TabLayout的使用,没见过用这个组件的源代码,想从它里面学习学习。就当是见见世面,顺便写个笔记。

先从印象到布局

原生闹钟给我的印象非常好,有种说不出的美感。在改工作上的问题时,顺便看它的样式,大致就是一个TabLayout加上四个fragment

首先是头部的四个图标,是一个嵌在了Toolbar中的TabLayoutToolbar也是一个ViewGroup,在它的里面放一个其他的控件也是理所当然,只是以前没试过。第一次看到这个写法,感觉非常好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
app:elevation="0dp">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentInsetStart="0dp"
tools:ignore="RtlSymmetry">
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:tabGravity="fill"
app:tabIndicatorColor="@android:color/transparent"
app:tabMaxWidth="0dp"
app:tabMode="fixed"
app:tabPaddingEnd="0dp"
app:tabPaddingStart="0dp" />
</android.support.v7.widget.Toolbar>

然后在Toolbar下面就是一个ViewPager,存放在一个FrameLayout中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">

<android.support.v4.view.ViewPager
android:id="@+id/desk_clock_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:saveEnabled="false" />

<include layout="@layout/drop_shadow" />

</FrameLayout>

这样基本上大致的框架就出来了。接着看对它的初始化:
基本步骤和网上的教程是差不多的,只是其中的UiDataModel感觉非常有意思,感觉它几乎无所不能,用到的任何东西都可以往里面取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Create the tabs that make up the user interface.
mTabLayout = (TabLayout) findViewById(R.id.tabs);
final int tabCount = UiDataModel.getUiDataModel().getTabCount();
final boolean showTabLabel = getResources().getBoolean(R.bool.showTabLabel);
final boolean showTabHorizontally = getResources().getBoolean(R.bool.showTabHorizontally);
for (int i = 0; i < tabCount; i++) {
final UiDataModel.Tab tabModel = UiDataModel.getUiDataModel().getTab(i);
final @StringRes int labelResId = tabModel.getLabelResId();

final TabLayout.Tab tab = mTabLayout.newTab()
.setTag(tabModel)
.setIcon(tabModel.getIconResId())
.setContentDescription(labelResId);

if (showTabLabel) {
tab.setText(labelResId);
tab.setCustomView(R.layout.tab_item);

@SuppressWarnings("ConstantConditions")
final TextView text = (TextView) tab.getCustomView()
.findViewById(android.R.id.text1);
text.setTextColor(mTabLayout.getTabTextColors());

// Bind the icon to the TextView.
final Drawable icon = tab.getIcon();
if (showTabHorizontally) {
// Remove the icon so it doesn't affect the minimum TabLayout height.
tab.setIcon(null);
text.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
} else {
text.setCompoundDrawablesRelativeWithIntrinsicBounds(null, icon, null, null);
}
}

打开UiDataModel,第一个引起我的关注的是一个枚举型,在其中保存了有关Tab的基本信息,如文本、图标资源id以及该Tab所对应的fragment等。第一次见到这种写法,有点震惊,同时觉得对于这种静态的应用,用枚举型去保存也符合常理,我觉得这种方法可以以后利用在我写的Android项目中,比如说我的应用中,Tab也是固定的,同时这样写可以让那些信息更加集中,修改起来更加方便,让代码的逻辑性更强。看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** Identifies each of the primary tabs within the application. */
public enum Tab {
ALARMS(AlarmClockFragment.class, R.drawable.ic_tab_alarm, R.string.menu_alarm),
CLOCKS(ClockFragment.class, R.drawable.ic_tab_clock, R.string.menu_clock),
TIMERS(TimerFragment.class, R.drawable.ic_tab_timer, R.string.menu_timer),
STOPWATCH(StopwatchFragment.class, R.drawable.ic_tab_stopwatch, R.string.menu_stopwatch);

private final String mFragmentClassName;
private final @DrawableRes int mIconResId;
private final @StringRes int mLabelResId;

Tab(Class fragmentClass, @DrawableRes int iconResId, @StringRes int labelResId) {
mFragmentClassName = fragmentClass.getName();
mIconResId = iconResId;
mLabelResId = labelResId;
}

public String getFragmentClassName() { return mFragmentClassName; }
public @DrawableRes int getIconResId() { return mIconResId; }
public @StringRes int getLabelResId() { return mLabelResId; }
}

感觉自己简直被这源代码所陶醉。后面继续看关于TabLayout的初始化。这其中,对其的适配器的写法,我也是比较感兴趣的。因为自己在写应用的时候,遇到过关于PagerAdapter的问题,所以比较期待它是如何去实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Customize the view pager.
mFragmentTabPagerAdapter = new FragmentTabPagerAdapter(this);
mFragmentTabPager = (ViewPager) findViewById(R.id.desk_clock_pager);
// Keep all four tabs to minimize jank.
mFragmentTabPager.setOffscreenPageLimit(3);
// Set Accessibility Delegate to null so view pager doesn't intercept movements and
// prevent the fab from being selected.
mFragmentTabPager.setAccessibilityDelegate(null);
// Mirror changes made to the selected page of the view pager into UiDataModel.
mFragmentTabPager.addOnPageChangeListener(new PageChangeWatcher());
mFragmentTabPager.setAdapter(mFragmentTabPagerAdapter);

// Mirror changes made to the selected tab into UiDataModel.
mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
UiDataModel.getUiDataModel().setSelectedTab((UiDataModel.Tab) tab.getTag());
}

@Override
public void onTabUnselected(TabLayout.Tab tab) {
}

@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});

适配器的源代码不完全贴出来,感觉都对不起这源代码。它继承自ViewPager,自己实现了其中的方法,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/**
* This adapter produces the DeskClockFragments that are the content of the DeskClock tabs. The
* adapter presents the tabs in LTR and RTL order depending on the text layout direction for the
* current locale. To prevent issues when switching between LTR and RTL, fragments are registered
* with the manager using position-independent tags, which is an important departure from
* FragmentPagerAdapter.
*/
final class FragmentTabPagerAdapter extends PagerAdapter {

private final DeskClock mDeskClock;

/** The manager into which fragments are added. */
private final FragmentManager mFragmentManager;

/** A fragment cache that can be accessed before {@link #instantiateItem} is called. */
private final Map<UiDataModel.Tab, DeskClockFragment> mFragmentCache;

/** The active fragment transaction if one exists. */
private FragmentTransaction mCurrentTransaction;

/** The current fragment displayed to the user. */
private Fragment mCurrentPrimaryItem;

FragmentTabPagerAdapter(DeskClock deskClock) {
mDeskClock = deskClock;
mFragmentCache = new ArrayMap<>(getCount());
mFragmentManager = deskClock.getFragmentManager();
}

@Override
public int getCount() {
return UiDataModel.getUiDataModel().getTabCount();
}

/**
* @param position the left-to-right index of the fragment to be returned
* @return the fragment displayed at the given {@code position}
*/
DeskClockFragment getDeskClockFragment(int position) {
// Fetch the tab the UiDataModel reports for the position.
final UiDataModel.Tab tab = UiDataModel.getUiDataModel().getTabAt(position);

// First check the local cache for the fragment.
DeskClockFragment fragment = mFragmentCache.get(tab);
if (fragment != null) {
return fragment;
}

// Next check the fragment manager; relevant when app is rebuilt after locale changes
// because this adapter will be new and mFragmentCache will be empty, but the fragment
// manager will retain the Fragments built on original application launch.
fragment = (DeskClockFragment) mFragmentManager.findFragmentByTag(tab.name());
if (fragment != null) {
fragment.setFabContainer(mDeskClock);
mFragmentCache.put(tab, fragment);
return fragment;
}

// Otherwise, build the fragment from scratch.
final String fragmentClassName = tab.getFragmentClassName();
fragment = (DeskClockFragment) Fragment.instantiate(mDeskClock, fragmentClassName);
fragment.setFabContainer(mDeskClock);
mFragmentCache.put(tab, fragment);
return fragment;
}

@Override
public void startUpdate(ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this + " has no id");
}
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurrentTransaction == null) {
mCurrentTransaction = mFragmentManager.beginTransaction();
}

// Use the fragment located in the fragment manager if one exists.
final UiDataModel.Tab tab = UiDataModel.getUiDataModel().getTabAt(position);
Fragment fragment = mFragmentManager.findFragmentByTag(tab.name());
if (fragment != null) {
mCurrentTransaction.attach(fragment);
} else {
fragment = getDeskClockFragment(position);
mCurrentTransaction.add(container.getId(), fragment, tab.name());
}

if (fragment != mCurrentPrimaryItem) {
FragmentCompat.setMenuVisibility(fragment, false);
FragmentCompat.setUserVisibleHint(fragment, false);
}

return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurrentTransaction == null) {
mCurrentTransaction = mFragmentManager.beginTransaction();
}
final DeskClockFragment fragment = (DeskClockFragment) object;
fragment.setFabContainer(null);
mCurrentTransaction.detach(fragment);
}

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
final Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
FragmentCompat.setMenuVisibility(mCurrentPrimaryItem, false);
FragmentCompat.setUserVisibleHint(mCurrentPrimaryItem, false);
}
if (fragment != null) {
FragmentCompat.setMenuVisibility(fragment, true);
FragmentCompat.setUserVisibleHint(fragment, true);
}
mCurrentPrimaryItem = fragment;
}
}

@Override
public void finishUpdate(ViewGroup container) {
if (mCurrentTransaction != null) {
mCurrentTransaction.commitAllowingStateLoss();
mCurrentTransaction = null;
mFragmentManager.executePendingTransactions();
}
}

@Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment) object).getView() == view;
}
}

修改framework后如何编译、生效!生效!

被framework生效问题困了一天, 一定要记下来。试了网上各种答案,得到的结果都没生效。最终还是从同事那里得到的一份答案,还是同事靠谱啊。一、framework编译方法一般修改framework层的内容分为两种,一种是res,一种是源代码。前者只需要在其目录下,通过mm的方式即可将framewo
阅读更多

插入耳机时安全音量提示

插入耳机的状态下,当音量达到某个值得时候,会弹出一个“继续提高音量将损害听力”的提示框。这只是一普通的对话框而已,但是这与需求不太一样,需要做一些微微的调整。源码的目录:Android 8.0中,位于:frameworks/base/packages/SystemUI/src/com/android
阅读更多

如何下载并导入Android系统源代码到Android Studio

下载源代码对于下载源代码这种操作,官方给的说明确实也是很详细。但是奈何GFW。所以用国内的源跑得比什么都快。网上也有很多教程,但是这些感觉是copy——因为它们都比不上国内源的网站上给的操作说明。国内有哪些Android的镜像源1. 中国科技大学2. 清华大学从上面的两个链接直接点进去便是帮助文档,
阅读更多

Android开发工具系之ADB

ADB的相关概念&工作原理通过WLAN使用ADB感觉这个挺有意思,但是又在情理之中。1、电脑与设备连入同一个局域网,并能连通。2、将设备接上电脑,设置端口。λ adb tcpip 5555restarting in TCP mode port: 55553、断开设备4、找出设备的IP地址5、
阅读更多

Android中的apk打包

前言

使用友盟对应用进行信息收集时,其中包含有一个渠道名。渠道姑且可以认为是一个商店吧,如果应用要在很多个商店上面上架的话,一直改太麻烦了。有一个叫做多渠道打包的东西自然而然地走了过来。

多渠道打包实现

1
2
3
4
5
6
<meta-data
android:name="UMENG_APPKEY"
android:value="xxxxxxxxxxxxxxxxxxxxxx" />
<meta-data
android:name="UMENG_CHANNEL"
android:value="Google Play Store" />

如上所示,如果需要换一个渠道的话,重新改的话就特别麻烦了。先将其中的value替换成占位符${UMENG_CHANNEL_VALUE}。接下来到模块下的build.gradle中进行相应的修改。修改大致如下:

1
2
3
<meta-data
android:name="UMENG_CHANNEL"
android:value="${UMENG_CHANNEL_NAME}" />

接下来配置build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
defaultConfig {
...
// 默认是umeng的渠道
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]
}

// 友盟多渠道打包
flavorDimensions "wtf"
productFlavors{
google {
dimension "wtf"
}
coolapk {
dimension "wtf"
}
}

productFlavors.all { flavor ->
flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
}

自动设置应用签名

在buildType{}前添加下段,并在buildType的release中添加signingConfig signingConfigs.release

1
2
3
4
5
6
7
8
9
10
11
12
signingConfigs {
debug {
// No debug config
}

release {
storeFile file("../yourapp.keystore")
storePassword "your password"
keyAlias "your alias"
keyPassword "your password"
}
}

打release版本的包时就会使用其中所配置的签名了。

修改AS生成的apk默认名

不同gradle版本间存在一些差异,如果报错了,google修改一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
applicationVariants.all { variant ->
variant.outputs.all { output ->
// outputFileName = new File(
// "JPreader-" + ${variant.productFlavors[0].name} +
// buildType.name + "-v" +
// defaultConfig.versionName + "-" +
// defaultConfig.versionCode + ".apk" )
if (outputFile != null && outputFile.name.endsWith('.apk')) {
// 输出apk名称为ruijie_v1.0_wandoujia.apk
def fileName = "JPreader_v${defaultConfig.versionName}_${variant.productFlavors[0].name}.apk"
outputFileName = new File(fileName)
}
}
}

小结

build.gradle真的是神奇,有一些用法还是可以去学学。当前的build.gradle文件的整体如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
defaultConfig {
applicationId "cn.xuchuanjun.nhknews"
minSdkVersion 19
targetSdkVersion 26
versionCode 2
versionName "1.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// jackOptions {
// enabled true
// }
//multiDexEnable true //突破应用方法数65535的一个限制
manifestPlaceholders=[UMENG_CHANNEL_NAME:"Google Play Store"]
}

signingConfigs {
debug {

}
myReleaseConfig {
storeFile file("xxxxxxxxxxxxxxxxx.jks")
storePassword "xxxxxxxx"
keyAlias "xxxxxx"
keyPassword "xxxxxxxx"
}
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.myReleaseConfig

applicationVariants.all { variant ->
variant.outputs.all { output ->
// outputFileName = new File(
// "JPreader-" + ${variant.productFlavors[0].name} +
// buildType.name + "-v" +
// defaultConfig.versionName + "-" +
// defaultConfig.versionCode + ".apk" )
if (outputFile != null && outputFile.name.endsWith('.apk')) {
// 输出apk名称为ruijie_v1.0_wandoujia.apk
def fileName = "JPreader_v${defaultConfig.versionName}_${variant.productFlavors[0].name}.apk"
outputFileName = new File(fileName)
}
}

}
}
}
flavorDimensions "wtf"
productFlavors{
google {
dimension "wtf"
}
coolapk {
dimension "wtf"
}
}

productFlavors.all{
flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_NAME:name]
}

compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
repositories {
flatDir {
dirs 'libs'
}
}
}

Android中的签名

签名,顾名思义与生活中的签名类似,为某个东西签了名,那么这个东西就与所签的名字产生了某种关系,如归属等。

为什么要为Android应用签名?

这是因为Android系统的要求就是这样,Android系统要求每一个Android应用程序必须要经过数字签名才能够安装到系统中,也就是说如果一个Android应用程序没有经过数字签名,就无法安装到系统中。

为什么在AS中直接RUN可以安装到系统上?
因为这种方式会使用Android Studio默认生成的debug签名,去给应用进行签名。

签名不同会怎样

如果同一应用使用不同的签名,那么将不能覆盖安装,必须先卸载之前的,然后再安装。

1)两个程序的入口Activity是否相同。两个程序如果包名不一样,即使其它所有代码完全一样,也不会被视为同一个程序的不同版本; 2)两个程序所采用的签名是否相同。如果两个程序所采用的签名不同,即使包名相同,也不会被视为同一个程序的不同版本,不能覆盖安装。

所以这也是为什么,同样一份代码,由不同的机器RUN,然后安装到同一台设备上时,需要先卸载之前的应用,而后再安装此次的。

原因就是每台机器默认生成的debug签名都不一样!

结论

应用商城不接受用debug签名签的应用,必须使用自己的签名。

使用自己的签名可以避免应用不具备升级功能。